n8n-nodes-tembory 1.0.27 → 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 +9 -1
- package/dist/nodes/Mem0/Mem0Memory.node.js +59 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
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.
|
|
6
14
|
|
|
7
15
|
## 1.0.27
|
|
8
16
|
|
|
@@ -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;
|
|
@@ -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)
|
|
@@ -1735,7 +1777,9 @@ const compactVectorMemoriesForAgent = (vectorMemories = [], toolHistory = [], ma
|
|
|
1735
1777
|
return (vectorMemories || [])
|
|
1736
1778
|
.map((memory) => contextMemoryText(memory, 220))
|
|
1737
1779
|
.filter(Boolean)
|
|
1780
|
+
.filter((text) => !/^\[recent_message\]/i.test(text))
|
|
1738
1781
|
.filter((text) => !hasStructuredTools || !/^\[tool_events_extracted\]/i.test(text))
|
|
1782
|
+
.filter((text) => !hasStructuredTools || !/^\[Used tools:/i.test(text))
|
|
1739
1783
|
.slice(0, maxItems);
|
|
1740
1784
|
};
|
|
1741
1785
|
const modelResponseText = (response) => {
|
|
@@ -2605,7 +2649,7 @@ class Mem0Memory {
|
|
|
2605
2649
|
saveContext: async (inputValues = {}, outputValues = {}) => {
|
|
2606
2650
|
loadCache.clear();
|
|
2607
2651
|
const input = pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']);
|
|
2608
|
-
const output = pickText(outputValues, ['output', 'response', 'text', 'answer']);
|
|
2652
|
+
const output = cleanAssistantTranscriptText(pickText(outputValues, ['output', 'response', 'text', 'answer']));
|
|
2609
2653
|
if (input)
|
|
2610
2654
|
currentMessages.push(toBaseMessage({ role: 'user', content: input }));
|
|
2611
2655
|
if (output)
|
|
@@ -2641,10 +2685,12 @@ class Mem0Memory {
|
|
|
2641
2685
|
if (inputParts.length)
|
|
2642
2686
|
inputValues.input = inputParts.join('\n');
|
|
2643
2687
|
const toolContext = toolCalls.length
|
|
2644
|
-
?
|
|
2645
|
-
:
|
|
2646
|
-
if (outputParts.length
|
|
2647
|
-
outputValues.output =
|
|
2688
|
+
? toolCalls
|
|
2689
|
+
: [];
|
|
2690
|
+
if (outputParts.length)
|
|
2691
|
+
outputValues.output = outputParts.join('\n').trim();
|
|
2692
|
+
if (toolContext.length)
|
|
2693
|
+
outputValues.__temboryToolCalls = toolContext;
|
|
2648
2694
|
if (inputValues.input || outputValues.output)
|
|
2649
2695
|
await Mem0Memory.prototype.saveContextForItem.call(this, itemIndex, inputValues, outputValues);
|
|
2650
2696
|
}
|
|
@@ -2655,7 +2701,8 @@ class Mem0Memory {
|
|
|
2655
2701
|
const store = getMemoryStore(this);
|
|
2656
2702
|
const key = userKeyFrom(threadId, adv, project);
|
|
2657
2703
|
const input = stripThreadTestPrefix(pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']));
|
|
2658
|
-
const
|
|
2704
|
+
const rawOutput = pickText(outputValues, ['output', 'response', 'text', 'answer']);
|
|
2705
|
+
const output = cleanAssistantTranscriptText(rawOutput);
|
|
2659
2706
|
const toolCalls = extractToolCalls(outputValues);
|
|
2660
2707
|
const recentForMem0 = [];
|
|
2661
2708
|
const profileFromTurn = extractProfileFactsFromText(input, 'user_message', nowIso());
|
|
@@ -2777,6 +2824,8 @@ class Mem0Memory {
|
|
|
2777
2824
|
kind: 'tool_facts',
|
|
2778
2825
|
source: 'n8n_connected_embedding',
|
|
2779
2826
|
}, ids));
|
|
2827
|
+
}
|
|
2828
|
+
if (adv.includeToolHistory !== false && toolCalls.length) {
|
|
2780
2829
|
for (const tool of toolCalls) {
|
|
2781
2830
|
clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeToolCall(tool, threadId), {
|
|
2782
2831
|
kind: 'tool_history',
|
|
@@ -3588,9 +3637,11 @@ exports.__private = {
|
|
|
3588
3637
|
toolHistoryItemsFromMemory,
|
|
3589
3638
|
explicitToolHistoryItemsFromMemory,
|
|
3590
3639
|
toolHistoryFromMemory,
|
|
3640
|
+
cleanAssistantTranscriptText,
|
|
3591
3641
|
recentMessageFromMemory,
|
|
3592
3642
|
previousUserFallbackFromWorkingMemory,
|
|
3593
3643
|
firstUserMessageFromConversation,
|
|
3644
|
+
sortConversationChronological,
|
|
3594
3645
|
pruneConversationMessagesPreserveAnchors,
|
|
3595
3646
|
dedupeToolHistory,
|
|
3596
3647
|
applyToolHistoryWindow,
|
package/package.json
CHANGED