n8n-nodes-tembory 1.0.27 → 1.0.29
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 +15 -1
- package/dist/nodes/Mem0/Mem0Memory.node.js +87 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Node de memoria operacional da Tembory para agentes de IA no n8n.
|
|
4
4
|
|
|
5
|
-
Versao atual: `1.0.
|
|
5
|
+
Versao atual: `1.0.29`.
|
|
6
|
+
|
|
7
|
+
## 1.0.29
|
|
8
|
+
|
|
9
|
+
- Considera disponibilidade recente no transcript como contexto operacional valido quando `tool_history` ainda nao foi recuperado no turno.
|
|
10
|
+
- Evita repetir `agenda_consultar_disponibilidade` apos o agente ja ter respondido vagas no historico recente.
|
|
11
|
+
- Direciona selecao de horario como "quero dia 13" para `agenda_pre_reservar_horario` quando ha vagas recentes, mesmo sem marker estruturado no contexto atual.
|
|
12
|
+
|
|
13
|
+
## 1.0.28
|
|
14
|
+
|
|
15
|
+
- Limpa blocos tecnicos de tool (`Used tools` / `Calling ...`) antes de salvar mensagens do assistente no transcript.
|
|
16
|
+
- Mantem tool history estruturado separado do texto da conversa, inclusive no modo com embedding conectado pelo n8n.
|
|
17
|
+
- Ordena o frame de conversa com mensagem do usuario antes da resposta do assistente quando o timestamp empata.
|
|
18
|
+
- Extrai fatos basicos do usuario como nome, idade, cidade e interesses a partir de mensagens normais.
|
|
19
|
+
- Reduz duplicacao no contexto compacto removendo markers de recent message/tool quando ja ha secoes estruturadas.
|
|
6
20
|
|
|
7
21
|
## 1.0.27
|
|
8
22
|
|
|
@@ -190,6 +190,15 @@ const withTemboryScore = (m, temboryScore) => {
|
|
|
190
190
|
return { ...m, temboryScore };
|
|
191
191
|
};
|
|
192
192
|
const normalizeFactValue = (value) => String(value || '').replace(/\*\*/g, '').replace(/\s+/g, ' ').replace(/[.,;:]+$/g, '').trim();
|
|
193
|
+
const cleanAssistantTranscriptText = (value) => {
|
|
194
|
+
let text = String(value || '');
|
|
195
|
+
if (!text.trim())
|
|
196
|
+
return '';
|
|
197
|
+
text = text.replace(/^\s*\[Used tools:[\s\S]*?\]\]\s*/i, '');
|
|
198
|
+
text = text.replace(/^Calling\s+[A-Za-z0-9_.:-]+\s+with input:\s*\{[^\n]*\}\s*/gim, '');
|
|
199
|
+
text = text.replace(/^\s*\[tool_events_extracted\][^\n]*\n?/gim, '');
|
|
200
|
+
return text.replace(/\n{3,}/g, '\n\n').trim();
|
|
201
|
+
};
|
|
193
202
|
const profileSourceRank = (source = '') => {
|
|
194
203
|
if (/^user_message$/i.test(source))
|
|
195
204
|
return 100;
|
|
@@ -294,9 +303,17 @@ const extractProfileFactsFromText = (text, source = 'message', at = nowIso()) =>
|
|
|
294
303
|
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);
|
|
295
304
|
if (phone && canExtractStrongProfileFacts)
|
|
296
305
|
setProfileFact(facts, 'phone', (phone[1] || phone[0]).trim(), source, at);
|
|
297
|
-
const name = /(?:meu nome (?:e
|
|
306
|
+
const name = /(?:meu nome (?:e|é|eh|he)|me chamo|sou o|sou a)\s+([A-ZÀ-Ú][A-Za-zÀ-ÿ]+(?:\s+[A-ZÀ-Ú][A-Za-zÀ-ÿ]+){0,3})/i.exec(content);
|
|
298
307
|
if (name && canExtractStrongProfileFacts)
|
|
299
308
|
setProfileFact(facts, 'name', name[1], source, at);
|
|
309
|
+
const age = /(?:tenho|idade\s*(?:e|é|:)?|sou de)\s+(\d{1,3})\s*anos?\b/i.exec(content);
|
|
310
|
+
if (age && canExtractStrongProfileFacts)
|
|
311
|
+
setProfileFact(facts, 'age', age[1], source, at);
|
|
312
|
+
const city = /(?:moro em|sou de|vivo em|resido em)\s+([A-Za-zÀ-ÿ][A-Za-zÀ-ÿ\s'.-]{1,80})/i.exec(content);
|
|
313
|
+
if (city && canExtractStrongProfileFacts) {
|
|
314
|
+
const cityValue = city[1].replace(/\s+(?:e|mas|porque|pois|com|pra|para)\b.*$/i, '').trim();
|
|
315
|
+
setProfileFact(facts, 'city', cityValue, source, at);
|
|
316
|
+
}
|
|
300
317
|
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);
|
|
301
318
|
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();
|
|
302
319
|
if (companyValue && canExtractStrongProfileFacts && isPlausibleCompanyValue(companyValue))
|
|
@@ -318,6 +335,9 @@ const extractProfileFactsFromText = (text, source = 'message', at = nowIso()) =>
|
|
|
318
335
|
const interest = /(?:interesse|interessado|preciso|quero|busco)\s+(?:em|de|por)?\s*(.{4,120})/i.exec(content);
|
|
319
336
|
if (interest)
|
|
320
337
|
addProfileListFact(facts, 'interests', interest[1], source, at);
|
|
338
|
+
const likes = /(?:gosto de|curto|adoro)\s+(.{3,120})/i.exec(content);
|
|
339
|
+
if (likes)
|
|
340
|
+
addProfileListFact(facts, 'interests', likes[1], source, at);
|
|
321
341
|
return facts;
|
|
322
342
|
};
|
|
323
343
|
const mergeProfileFacts = (...factSets) => {
|
|
@@ -388,11 +408,32 @@ const safeParseToolPayload = (value) => {
|
|
|
388
408
|
const compactToolPayload = (value) => truncate(typeof value === 'string' ? value : safeStringify(value), 900);
|
|
389
409
|
const maybeToolResult = (tool, includeResults = true) => includeResults === false ? undefined : compactToolPayload(safeParseToolPayload(tool === null || tool === void 0 ? void 0 : tool.result));
|
|
390
410
|
const containsSelectedSlot = (text = '') => /\b(?:às|as|das|para)?\s*\d{1,2}(?::\d{2}|h\d{0,2})?\b/i.test(String(text || '')) || /\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/.test(String(text || ''));
|
|
411
|
+
const availabilityFromRecentMessages = (recentMessages = []) => {
|
|
412
|
+
const messages = Array.isArray(recentMessages) ? recentMessages : [];
|
|
413
|
+
const candidates = [...messages].reverse().filter((message) => {
|
|
414
|
+
const role = String(message.role || '').toLowerCase();
|
|
415
|
+
const text = String(message.content || message.text || message.memory || '');
|
|
416
|
+
if (role && role !== 'assistant' && role !== 'ai' && role !== 'system')
|
|
417
|
+
return false;
|
|
418
|
+
return /\b(?:tem|tenho|há|ha)\s+vagas?\b|\bop[cç][oõ]es?\s+dispon[ií]veis\b|\bavailable_slots\b/i.test(text)
|
|
419
|
+
&& (/\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/.test(text) || /\b\d{1,2}:\d{2}\b/.test(text));
|
|
420
|
+
});
|
|
421
|
+
const latest = candidates[0];
|
|
422
|
+
if (!latest)
|
|
423
|
+
return null;
|
|
424
|
+
return {
|
|
425
|
+
text: truncate(String(latest.content || latest.text || latest.memory || ''), 900),
|
|
426
|
+
at: latest.at || latest.created_at || latest.createdAt || nowIso(),
|
|
427
|
+
source: latest.source || 'recent_message',
|
|
428
|
+
};
|
|
429
|
+
};
|
|
391
430
|
const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessages = [], includeResults = true) => {
|
|
392
431
|
const tools = Array.isArray(toolHistory) ? toolHistory : [];
|
|
393
432
|
const successfulTools = tools.filter((tool) => tool.ok !== false);
|
|
394
433
|
const byName = (name) => successfulTools.filter((tool) => tool.name === name);
|
|
395
434
|
const availability = byName('agenda_consultar_disponibilidade');
|
|
435
|
+
const recentAvailability = availability.length ? null : availabilityFromRecentMessages(recentMessages);
|
|
436
|
+
const hasAvailability = availability.length > 0 || Boolean(recentAvailability);
|
|
396
437
|
const reservations = byName('agenda_pre_reservar_horario');
|
|
397
438
|
const confirmations = byName('agenda_confirmar_agendamento');
|
|
398
439
|
const lastTool = tools[tools.length - 1] || null;
|
|
@@ -400,18 +441,18 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
400
441
|
const lastConfirmation = confirmations[confirmations.length - 1] || null;
|
|
401
442
|
const hasPendingReservation = Boolean(lastReservation && (!lastConfirmation || String(lastReservation.at || '') > String(lastConfirmation.at || '')));
|
|
402
443
|
const blockedWithoutContext = [];
|
|
403
|
-
if (!
|
|
444
|
+
if (!hasAvailability)
|
|
404
445
|
blockedWithoutContext.push('agenda_pre_reservar_horario');
|
|
405
446
|
if (!reservations.length)
|
|
406
447
|
blockedWithoutContext.push('agenda_confirmar_agendamento');
|
|
407
448
|
const guidance = [];
|
|
408
|
-
if (!
|
|
449
|
+
if (!hasAvailability)
|
|
409
450
|
guidance.push('No availability result is known for this session; consult availability before reserving or confirming.');
|
|
410
451
|
else if (hasPendingReservation)
|
|
411
452
|
guidance.push('A pre-reservation exists after the latest confirmation; confirmation can use the latest pre-reservation context.');
|
|
412
453
|
else if (lastConfirmation)
|
|
413
454
|
guidance.push('The latest reservation appears confirmed; do not confirm again unless the user explicitly asks to repeat or change it.');
|
|
414
|
-
else if (
|
|
455
|
+
else if (hasAvailability)
|
|
415
456
|
guidance.push('Availability is known; if the user chooses one listed slot, reserve without repeating availability.');
|
|
416
457
|
return {
|
|
417
458
|
profile_complete: Boolean(profileFacts && profileFacts.name && profileFacts.company && profileFacts.email && profileFacts.phone),
|
|
@@ -425,11 +466,12 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
425
466
|
agenda_confirmar_agendamento: confirmations.length,
|
|
426
467
|
},
|
|
427
468
|
agenda_state: {
|
|
428
|
-
has_availability:
|
|
469
|
+
has_availability: hasAvailability,
|
|
429
470
|
has_pre_reservation: reservations.length > 0,
|
|
430
471
|
has_confirmation: confirmations.length > 0,
|
|
431
472
|
has_pending_pre_reservation: hasPendingReservation,
|
|
432
|
-
latest_availability_result: availability.length ? maybeToolResult(availability[availability.length - 1], includeResults) : null,
|
|
473
|
+
latest_availability_result: availability.length ? maybeToolResult(availability[availability.length - 1], includeResults) : (recentAvailability && includeResults !== false ? recentAvailability.text : null),
|
|
474
|
+
latest_availability_source: availability.length ? 'tool_history' : (recentAvailability ? recentAvailability.source : null),
|
|
433
475
|
latest_pre_reservation_result: lastReservation ? maybeToolResult(lastReservation, includeResults) : null,
|
|
434
476
|
latest_confirmation_result: lastConfirmation ? maybeToolResult(lastConfirmation, includeResults) : null,
|
|
435
477
|
},
|
|
@@ -821,6 +863,16 @@ const extractToolCalls = (outputValues = {}) => {
|
|
|
821
863
|
source: meta.source || 'intermediate_steps',
|
|
822
864
|
}, calls.length + 1, { at }));
|
|
823
865
|
};
|
|
866
|
+
if (Array.isArray(outputValues.__temboryToolCalls)) {
|
|
867
|
+
for (const tool of outputValues.__temboryToolCalls) {
|
|
868
|
+
push(tool.name || tool.tool || tool.toolName, tool.input || tool.toolInput || tool.args || '', tool.result || tool.output || tool.observation || '', tool.ok !== false, {
|
|
869
|
+
id: tool.id || tool.callId || tool.call_id || tool.toolCallId || tool.tool_call_id,
|
|
870
|
+
turnId: tool.turnId || tool.turn_id,
|
|
871
|
+
sequence: tool.sequence,
|
|
872
|
+
source: tool.source || 'n8n_tool_message',
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
824
876
|
readDeep(outputValues, (obj) => {
|
|
825
877
|
const name = obj.tool || obj.toolName || obj.name;
|
|
826
878
|
const hasInput = obj.toolInput !== undefined || obj.input !== undefined || obj.args !== undefined;
|
|
@@ -1116,9 +1168,21 @@ const appendCurrentUserMessage = (items = [], query = '') => {
|
|
|
1116
1168
|
return items || [];
|
|
1117
1169
|
return (items || []).concat([{ role: 'user', content: truncate(content, 2000), at: nowIso(), source: 'current_input' }]);
|
|
1118
1170
|
};
|
|
1171
|
+
const conversationRoleRank = (role = '') => /^(user|human)$/i.test(String(role)) ? 0 : /^(assistant|ai)$/i.test(String(role)) ? 1 : 2;
|
|
1172
|
+
const sortConversationChronological = (items = []) => [...(items || [])].sort((a, b) => {
|
|
1173
|
+
const atA = new Date(a.at || 0).getTime();
|
|
1174
|
+
const atB = new Date(b.at || 0).getTime();
|
|
1175
|
+
if (atA !== atB)
|
|
1176
|
+
return atA - atB;
|
|
1177
|
+
const roleA = conversationRoleRank(a.role);
|
|
1178
|
+
const roleB = conversationRoleRank(b.role);
|
|
1179
|
+
if (roleA !== roleB)
|
|
1180
|
+
return roleA - roleB;
|
|
1181
|
+
return String(a.content || '').localeCompare(String(b.content || ''));
|
|
1182
|
+
});
|
|
1119
1183
|
const pruneConversationMessagesPreserveAnchors = (items = [], limit = 8) => {
|
|
1120
1184
|
const normalizedLimit = Math.max(1, Number(limit) || 1);
|
|
1121
|
-
const chronological =
|
|
1185
|
+
const chronological = sortConversationChronological(items);
|
|
1122
1186
|
const firstUser = chronological.find((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
1123
1187
|
const tail = pruneByLimit(chronological, normalizedLimit);
|
|
1124
1188
|
if (!firstUser)
|
|
@@ -1735,7 +1799,9 @@ const compactVectorMemoriesForAgent = (vectorMemories = [], toolHistory = [], ma
|
|
|
1735
1799
|
return (vectorMemories || [])
|
|
1736
1800
|
.map((memory) => contextMemoryText(memory, 220))
|
|
1737
1801
|
.filter(Boolean)
|
|
1802
|
+
.filter((text) => !/^\[recent_message\]/i.test(text))
|
|
1738
1803
|
.filter((text) => !hasStructuredTools || !/^\[tool_events_extracted\]/i.test(text))
|
|
1804
|
+
.filter((text) => !hasStructuredTools || !/^\[Used tools:/i.test(text))
|
|
1739
1805
|
.slice(0, maxItems);
|
|
1740
1806
|
};
|
|
1741
1807
|
const modelResponseText = (response) => {
|
|
@@ -2605,7 +2671,7 @@ class Mem0Memory {
|
|
|
2605
2671
|
saveContext: async (inputValues = {}, outputValues = {}) => {
|
|
2606
2672
|
loadCache.clear();
|
|
2607
2673
|
const input = pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']);
|
|
2608
|
-
const output = pickText(outputValues, ['output', 'response', 'text', 'answer']);
|
|
2674
|
+
const output = cleanAssistantTranscriptText(pickText(outputValues, ['output', 'response', 'text', 'answer']));
|
|
2609
2675
|
if (input)
|
|
2610
2676
|
currentMessages.push(toBaseMessage({ role: 'user', content: input }));
|
|
2611
2677
|
if (output)
|
|
@@ -2641,10 +2707,12 @@ class Mem0Memory {
|
|
|
2641
2707
|
if (inputParts.length)
|
|
2642
2708
|
inputValues.input = inputParts.join('\n');
|
|
2643
2709
|
const toolContext = toolCalls.length
|
|
2644
|
-
?
|
|
2645
|
-
:
|
|
2646
|
-
if (outputParts.length
|
|
2647
|
-
outputValues.output =
|
|
2710
|
+
? toolCalls
|
|
2711
|
+
: [];
|
|
2712
|
+
if (outputParts.length)
|
|
2713
|
+
outputValues.output = outputParts.join('\n').trim();
|
|
2714
|
+
if (toolContext.length)
|
|
2715
|
+
outputValues.__temboryToolCalls = toolContext;
|
|
2648
2716
|
if (inputValues.input || outputValues.output)
|
|
2649
2717
|
await Mem0Memory.prototype.saveContextForItem.call(this, itemIndex, inputValues, outputValues);
|
|
2650
2718
|
}
|
|
@@ -2655,7 +2723,8 @@ class Mem0Memory {
|
|
|
2655
2723
|
const store = getMemoryStore(this);
|
|
2656
2724
|
const key = userKeyFrom(threadId, adv, project);
|
|
2657
2725
|
const input = stripThreadTestPrefix(pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']));
|
|
2658
|
-
const
|
|
2726
|
+
const rawOutput = pickText(outputValues, ['output', 'response', 'text', 'answer']);
|
|
2727
|
+
const output = cleanAssistantTranscriptText(rawOutput);
|
|
2659
2728
|
const toolCalls = extractToolCalls(outputValues);
|
|
2660
2729
|
const recentForMem0 = [];
|
|
2661
2730
|
const profileFromTurn = extractProfileFactsFromText(input, 'user_message', nowIso());
|
|
@@ -2777,6 +2846,8 @@ class Mem0Memory {
|
|
|
2777
2846
|
kind: 'tool_facts',
|
|
2778
2847
|
source: 'n8n_connected_embedding',
|
|
2779
2848
|
}, ids));
|
|
2849
|
+
}
|
|
2850
|
+
if (adv.includeToolHistory !== false && toolCalls.length) {
|
|
2780
2851
|
for (const tool of toolCalls) {
|
|
2781
2852
|
clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeToolCall(tool, threadId), {
|
|
2782
2853
|
kind: 'tool_history',
|
|
@@ -3588,9 +3659,12 @@ exports.__private = {
|
|
|
3588
3659
|
toolHistoryItemsFromMemory,
|
|
3589
3660
|
explicitToolHistoryItemsFromMemory,
|
|
3590
3661
|
toolHistoryFromMemory,
|
|
3662
|
+
cleanAssistantTranscriptText,
|
|
3663
|
+
availabilityFromRecentMessages,
|
|
3591
3664
|
recentMessageFromMemory,
|
|
3592
3665
|
previousUserFallbackFromWorkingMemory,
|
|
3593
3666
|
firstUserMessageFromConversation,
|
|
3667
|
+
sortConversationChronological,
|
|
3594
3668
|
pruneConversationMessagesPreserveAnchors,
|
|
3595
3669
|
dedupeToolHistory,
|
|
3596
3670
|
applyToolHistoryWindow,
|
package/package.json
CHANGED