n8n-nodes-tembory 1.0.26 → 1.0.28
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 +73 -19
- 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.28`.
|
|
6
|
+
|
|
7
|
+
## 1.0.28
|
|
8
|
+
|
|
9
|
+
- Limpa blocos tecnicos de tool (`Used tools` / `Calling ...`) antes de salvar mensagens do assistente no transcript.
|
|
10
|
+
- Mantem tool history estruturado separado do texto da conversa, inclusive no modo com embedding conectado pelo n8n.
|
|
11
|
+
- Ordena o frame de conversa com mensagem do usuario antes da resposta do assistente quando o timestamp empata.
|
|
12
|
+
- Extrai fatos basicos do usuario como nome, idade, cidade e interesses a partir de mensagens normais.
|
|
13
|
+
- Reduz duplicacao no contexto compacto removendo markers de recent message/tool quando ja ha secoes estruturadas.
|
|
14
|
+
|
|
15
|
+
## 1.0.27
|
|
16
|
+
|
|
17
|
+
- Aumenta a janela deterministica de conversa para 30 mensagens por padrao em todos os presets de producao.
|
|
18
|
+
- Entrega `conversation_history_chronological` e `all_user_messages_chronological` no frame de conversa.
|
|
19
|
+
- Para conversas curtas/medias, o historico completo recente vira a fonte primaria; vetor e resumo ficam como complemento.
|
|
6
20
|
|
|
7
21
|
## 1.0.26
|
|
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) => {
|
|
@@ -821,6 +841,16 @@ const extractToolCalls = (outputValues = {}) => {
|
|
|
821
841
|
source: meta.source || 'intermediate_steps',
|
|
822
842
|
}, calls.length + 1, { at }));
|
|
823
843
|
};
|
|
844
|
+
if (Array.isArray(outputValues.__temboryToolCalls)) {
|
|
845
|
+
for (const tool of outputValues.__temboryToolCalls) {
|
|
846
|
+
push(tool.name || tool.tool || tool.toolName, tool.input || tool.toolInput || tool.args || '', tool.result || tool.output || tool.observation || '', tool.ok !== false, {
|
|
847
|
+
id: tool.id || tool.callId || tool.call_id || tool.toolCallId || tool.tool_call_id,
|
|
848
|
+
turnId: tool.turnId || tool.turn_id,
|
|
849
|
+
sequence: tool.sequence,
|
|
850
|
+
source: tool.source || 'n8n_tool_message',
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
824
854
|
readDeep(outputValues, (obj) => {
|
|
825
855
|
const name = obj.tool || obj.toolName || obj.name;
|
|
826
856
|
const hasInput = obj.toolInput !== undefined || obj.input !== undefined || obj.args !== undefined;
|
|
@@ -925,7 +955,7 @@ const applyOperationalPreset = (advanced = {}) => {
|
|
|
925
955
|
topK: 8,
|
|
926
956
|
lastN: 12,
|
|
927
957
|
toolHistoryLastN: 15,
|
|
928
|
-
recentMessagesLastN:
|
|
958
|
+
recentMessagesLastN: 30,
|
|
929
959
|
},
|
|
930
960
|
productionBalanced: {
|
|
931
961
|
summarySource: 'auto',
|
|
@@ -958,7 +988,7 @@ const applyOperationalPreset = (advanced = {}) => {
|
|
|
958
988
|
topK: 6,
|
|
959
989
|
lastN: 8,
|
|
960
990
|
toolHistoryLastN: 10,
|
|
961
|
-
recentMessagesLastN:
|
|
991
|
+
recentMessagesLastN: 30,
|
|
962
992
|
vectorMemoryMaxChars: 360,
|
|
963
993
|
contextMaxChars: 10000,
|
|
964
994
|
connectedModelSummaryMaxChars: 1200,
|
|
@@ -995,7 +1025,7 @@ const applyOperationalPreset = (advanced = {}) => {
|
|
|
995
1025
|
topK: 4,
|
|
996
1026
|
lastN: 4,
|
|
997
1027
|
toolHistoryLastN: 5,
|
|
998
|
-
recentMessagesLastN:
|
|
1028
|
+
recentMessagesLastN: 30,
|
|
999
1029
|
vectorMemoryMaxChars: 260,
|
|
1000
1030
|
contextMaxChars: 7000,
|
|
1001
1031
|
connectedModelSummaryMaxChars: 1000,
|
|
@@ -1033,7 +1063,7 @@ const applyOperationalPreset = (advanced = {}) => {
|
|
|
1033
1063
|
lastN: 4,
|
|
1034
1064
|
maxReturn: 6,
|
|
1035
1065
|
toolHistoryLastN: 6,
|
|
1036
|
-
recentMessagesLastN:
|
|
1066
|
+
recentMessagesLastN: 30,
|
|
1037
1067
|
vectorMemoryMaxChars: 220,
|
|
1038
1068
|
contextMaxChars: 7000,
|
|
1039
1069
|
connectedModelSummaryMaxChars: 1200,
|
|
@@ -1063,7 +1093,7 @@ const applyOperationalPreset = (advanced = {}) => {
|
|
|
1063
1093
|
topK: 10,
|
|
1064
1094
|
lastN: 20,
|
|
1065
1095
|
toolHistoryLastN: 20,
|
|
1066
|
-
recentMessagesLastN:
|
|
1096
|
+
recentMessagesLastN: 30,
|
|
1067
1097
|
payloadFormat: 'auditJson',
|
|
1068
1098
|
},
|
|
1069
1099
|
};
|
|
@@ -1116,9 +1146,21 @@ const appendCurrentUserMessage = (items = [], query = '') => {
|
|
|
1116
1146
|
return items || [];
|
|
1117
1147
|
return (items || []).concat([{ role: 'user', content: truncate(content, 2000), at: nowIso(), source: 'current_input' }]);
|
|
1118
1148
|
};
|
|
1149
|
+
const conversationRoleRank = (role = '') => /^(user|human)$/i.test(String(role)) ? 0 : /^(assistant|ai)$/i.test(String(role)) ? 1 : 2;
|
|
1150
|
+
const sortConversationChronological = (items = []) => [...(items || [])].sort((a, b) => {
|
|
1151
|
+
const atA = new Date(a.at || 0).getTime();
|
|
1152
|
+
const atB = new Date(b.at || 0).getTime();
|
|
1153
|
+
if (atA !== atB)
|
|
1154
|
+
return atA - atB;
|
|
1155
|
+
const roleA = conversationRoleRank(a.role);
|
|
1156
|
+
const roleB = conversationRoleRank(b.role);
|
|
1157
|
+
if (roleA !== roleB)
|
|
1158
|
+
return roleA - roleB;
|
|
1159
|
+
return String(a.content || '').localeCompare(String(b.content || ''));
|
|
1160
|
+
});
|
|
1119
1161
|
const pruneConversationMessagesPreserveAnchors = (items = [], limit = 8) => {
|
|
1120
1162
|
const normalizedLimit = Math.max(1, Number(limit) || 1);
|
|
1121
|
-
const chronological =
|
|
1163
|
+
const chronological = sortConversationChronological(items);
|
|
1122
1164
|
const firstUser = chronological.find((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
1123
1165
|
const tail = pruneByLimit(chronological, normalizedLimit);
|
|
1124
1166
|
if (!firstUser)
|
|
@@ -1225,21 +1267,24 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
|
|
|
1225
1267
|
const previousUser = previousUserMessageForQuery(query, recentMessages);
|
|
1226
1268
|
const firstUser = firstUserMessageFromConversation(recentMessages);
|
|
1227
1269
|
const previousUserMessage = previousUser ? truncate(previousUser.content, 500) : previousUserFallbackFromWorkingMemory(query, workingMemory);
|
|
1228
|
-
const chronological =
|
|
1270
|
+
const chronological = (recentMessages || []).map((msg) => ({
|
|
1229
1271
|
role: msg.role,
|
|
1230
1272
|
content: truncate(msg.content, 500),
|
|
1231
1273
|
at: msg.at,
|
|
1232
1274
|
}));
|
|
1275
|
+
const userMessagesChronological = chronological.filter((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
1233
1276
|
const frame = cleanContextValue({
|
|
1234
1277
|
current_user_message: currentUser ? truncate(currentUser.content, 500) : truncate(query, 500),
|
|
1235
1278
|
first_user_message: firstUser ? truncate(firstUser.content, 500) : null,
|
|
1236
1279
|
previous_user_message: previousUserMessage,
|
|
1280
|
+
conversation_history_chronological: chronological,
|
|
1237
1281
|
recent_messages_chronological: chronological,
|
|
1282
|
+
all_user_messages_chronological: userMessagesChronological,
|
|
1238
1283
|
recent_user_messages: users
|
|
1239
1284
|
.filter((msg) => normalizeIntentText(msg.content).trim() !== normalizedQuery)
|
|
1240
1285
|
.slice(0, 5)
|
|
1241
1286
|
.map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at })),
|
|
1242
|
-
instruction: 'This is the authoritative short-term conversation frame. If the user asks about first/current/previous/last client messages, answer from first_user_message/current_user_message/previous_user_message/
|
|
1287
|
+
instruction: 'This is the authoritative short-term conversation frame. If the user asks about first/current/previous/last client messages or what they already said, answer from first_user_message/current_user_message/previous_user_message/conversation_history_chronological/all_user_messages_chronological before using vector memories, summaries, operational state, or tool history.',
|
|
1243
1288
|
});
|
|
1244
1289
|
if (!previousUserMessage)
|
|
1245
1290
|
frame.previous_user_message = null;
|
|
@@ -1732,7 +1777,9 @@ const compactVectorMemoriesForAgent = (vectorMemories = [], toolHistory = [], ma
|
|
|
1732
1777
|
return (vectorMemories || [])
|
|
1733
1778
|
.map((memory) => contextMemoryText(memory, 220))
|
|
1734
1779
|
.filter(Boolean)
|
|
1780
|
+
.filter((text) => !/^\[recent_message\]/i.test(text))
|
|
1735
1781
|
.filter((text) => !hasStructuredTools || !/^\[tool_events_extracted\]/i.test(text))
|
|
1782
|
+
.filter((text) => !hasStructuredTools || !/^\[Used tools:/i.test(text))
|
|
1736
1783
|
.slice(0, maxItems);
|
|
1737
1784
|
};
|
|
1738
1785
|
const modelResponseText = (response) => {
|
|
@@ -1934,8 +1981,8 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
1934
1981
|
section: 'context_header',
|
|
1935
1982
|
title: 'Tembory context',
|
|
1936
1983
|
value: compactForAgent || compactStateSections
|
|
1937
|
-
? 'Read-only memory. Conversation frame is authoritative for first/current/previous client messages. Follow next_expected_action when present. Before calling downstream tools, verify required prior tool context in tool_history or operational_state. Do not repeat tools listed in do_not_repeat_tools.'
|
|
1938
|
-
: 'Use this context as read-only memory. Prefer it over guessing. Do not mention internal section names to the user. The Conversation frame is authoritative for first, current, previous, and recent client messages. Treat next_expected_action as an instruction, not as a suggestion. If it says to call a tool now, call that tool instead of asking the user the same question again. If the user asks to continue, chooses a slot, says ok/sim, reserve, confirm, update, cancel, or performs 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.',
|
|
1984
|
+
? 'Read-only memory. Conversation frame is authoritative for the full recent transcript, including first/current/previous client messages. Follow next_expected_action when present. Before calling downstream tools, verify required prior tool context in tool_history or operational_state. Do not repeat tools listed in do_not_repeat_tools.'
|
|
1985
|
+
: 'Use this context as read-only memory. Prefer it over guessing. Do not mention internal section names to the user. The Conversation frame is authoritative for the full recent transcript, including first, current, previous, and recent client messages. Treat next_expected_action as an instruction, not as a suggestion. If it says to call a tool now, call that tool instead of asking the user the same question again. If the user asks to continue, chooses a slot, says ok/sim, reserve, confirm, update, cancel, or performs 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.',
|
|
1939
1986
|
});
|
|
1940
1987
|
}
|
|
1941
1988
|
sections.push({
|
|
@@ -2358,7 +2405,7 @@ class Mem0Memory {
|
|
|
2358
2405
|
values: [
|
|
2359
2406
|
{ displayName: 'Incluir Profile Facts', name: 'includeProfileFacts', type: 'boolean', default: true },
|
|
2360
2407
|
{ displayName: 'Incluir Mensagens Recentes', name: 'includeRecentMessages', type: 'boolean', default: true },
|
|
2361
|
-
{ displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default:
|
|
2408
|
+
{ displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default: 30 },
|
|
2362
2409
|
{ displayName: 'Incluir Highlights Recentes', name: 'includeRecentHighlights', type: 'boolean', default: true },
|
|
2363
2410
|
{ displayName: 'Máximo de Highlights', name: 'recentHighlightsMaxItems', type: 'number', default: 6 },
|
|
2364
2411
|
{ displayName: 'Incluir Grafo', name: 'includeRelations', type: 'boolean', default: true },
|
|
@@ -2513,7 +2560,7 @@ class Mem0Memory {
|
|
|
2513
2560
|
{ 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.' },
|
|
2514
2561
|
{ 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.' },
|
|
2515
2562
|
{ displayName: 'Incluir Mensagens Recentes', name: 'includeRecentMessages', type: 'boolean', default: true },
|
|
2516
|
-
{ displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default:
|
|
2563
|
+
{ displayName: 'Últimas N Mensagens', name: 'recentMessagesLastN', type: 'number', default: 30 },
|
|
2517
2564
|
{ displayName: 'Incluir Highlights Recentes', name: 'includeRecentHighlights', type: 'boolean', default: true },
|
|
2518
2565
|
{ displayName: 'Máximo de Highlights', name: 'recentHighlightsMaxItems', type: 'number', default: 6 },
|
|
2519
2566
|
],
|
|
@@ -2602,7 +2649,7 @@ class Mem0Memory {
|
|
|
2602
2649
|
saveContext: async (inputValues = {}, outputValues = {}) => {
|
|
2603
2650
|
loadCache.clear();
|
|
2604
2651
|
const input = pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']);
|
|
2605
|
-
const output = pickText(outputValues, ['output', 'response', 'text', 'answer']);
|
|
2652
|
+
const output = cleanAssistantTranscriptText(pickText(outputValues, ['output', 'response', 'text', 'answer']));
|
|
2606
2653
|
if (input)
|
|
2607
2654
|
currentMessages.push(toBaseMessage({ role: 'user', content: input }));
|
|
2608
2655
|
if (output)
|
|
@@ -2638,10 +2685,12 @@ class Mem0Memory {
|
|
|
2638
2685
|
if (inputParts.length)
|
|
2639
2686
|
inputValues.input = inputParts.join('\n');
|
|
2640
2687
|
const toolContext = toolCalls.length
|
|
2641
|
-
?
|
|
2642
|
-
:
|
|
2643
|
-
if (outputParts.length
|
|
2644
|
-
outputValues.output =
|
|
2688
|
+
? toolCalls
|
|
2689
|
+
: [];
|
|
2690
|
+
if (outputParts.length)
|
|
2691
|
+
outputValues.output = outputParts.join('\n').trim();
|
|
2692
|
+
if (toolContext.length)
|
|
2693
|
+
outputValues.__temboryToolCalls = toolContext;
|
|
2645
2694
|
if (inputValues.input || outputValues.output)
|
|
2646
2695
|
await Mem0Memory.prototype.saveContextForItem.call(this, itemIndex, inputValues, outputValues);
|
|
2647
2696
|
}
|
|
@@ -2652,7 +2701,8 @@ class Mem0Memory {
|
|
|
2652
2701
|
const store = getMemoryStore(this);
|
|
2653
2702
|
const key = userKeyFrom(threadId, adv, project);
|
|
2654
2703
|
const input = stripThreadTestPrefix(pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']));
|
|
2655
|
-
const
|
|
2704
|
+
const rawOutput = pickText(outputValues, ['output', 'response', 'text', 'answer']);
|
|
2705
|
+
const output = cleanAssistantTranscriptText(rawOutput);
|
|
2656
2706
|
const toolCalls = extractToolCalls(outputValues);
|
|
2657
2707
|
const recentForMem0 = [];
|
|
2658
2708
|
const profileFromTurn = extractProfileFactsFromText(input, 'user_message', nowIso());
|
|
@@ -2774,6 +2824,8 @@ class Mem0Memory {
|
|
|
2774
2824
|
kind: 'tool_facts',
|
|
2775
2825
|
source: 'n8n_connected_embedding',
|
|
2776
2826
|
}, ids));
|
|
2827
|
+
}
|
|
2828
|
+
if (adv.includeToolHistory !== false && toolCalls.length) {
|
|
2777
2829
|
for (const tool of toolCalls) {
|
|
2778
2830
|
clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeToolCall(tool, threadId), {
|
|
2779
2831
|
kind: 'tool_history',
|
|
@@ -3585,9 +3637,11 @@ exports.__private = {
|
|
|
3585
3637
|
toolHistoryItemsFromMemory,
|
|
3586
3638
|
explicitToolHistoryItemsFromMemory,
|
|
3587
3639
|
toolHistoryFromMemory,
|
|
3640
|
+
cleanAssistantTranscriptText,
|
|
3588
3641
|
recentMessageFromMemory,
|
|
3589
3642
|
previousUserFallbackFromWorkingMemory,
|
|
3590
3643
|
firstUserMessageFromConversation,
|
|
3644
|
+
sortConversationChronological,
|
|
3591
3645
|
pruneConversationMessagesPreserveAnchors,
|
|
3592
3646
|
dedupeToolHistory,
|
|
3593
3647
|
applyToolHistoryWindow,
|
package/package.json
CHANGED