n8n-nodes-tembory 1.0.24 → 1.0.26
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 +13 -1
- package/dist/nodes/Mem0/Mem0Memory.node.js +66 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,19 @@
|
|
|
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.26`.
|
|
6
|
+
|
|
7
|
+
## 1.0.26
|
|
8
|
+
|
|
9
|
+
- Adiciona `first_user_message` ao frame de conversa para perguntas como "qual foi minha primeira mensagem?".
|
|
10
|
+
- Preserva a primeira mensagem do usuario mesmo quando a janela recente e podada.
|
|
11
|
+
- Amplia a busca vetorial auxiliar de transcript para recuperar mensagens iniciais da sessao.
|
|
12
|
+
|
|
13
|
+
## 1.0.25
|
|
14
|
+
|
|
15
|
+
- Carrega a janela recente tambem a partir de memorias vetoriais com `kind=recent_message`.
|
|
16
|
+
- Adiciona uma busca auxiliar de transcript recente no modo BYO-AI para perguntas como "qual foi minha ultima mensagem" e "o que eu ja disse".
|
|
17
|
+
- Evita depender do endpoint legado `/v1/memories/` para reconstruir conversa curta.
|
|
6
18
|
|
|
7
19
|
## 1.0.24
|
|
8
20
|
|
|
@@ -1116,6 +1116,18 @@ const appendCurrentUserMessage = (items = [], query = '') => {
|
|
|
1116
1116
|
return items || [];
|
|
1117
1117
|
return (items || []).concat([{ role: 'user', content: truncate(content, 2000), at: nowIso(), source: 'current_input' }]);
|
|
1118
1118
|
};
|
|
1119
|
+
const pruneConversationMessagesPreserveAnchors = (items = [], limit = 8) => {
|
|
1120
|
+
const normalizedLimit = Math.max(1, Number(limit) || 1);
|
|
1121
|
+
const chronological = [...(items || [])].sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
|
|
1122
|
+
const firstUser = chronological.find((msg) => /^(user|human)$/i.test(String(msg.role || '')));
|
|
1123
|
+
const tail = pruneByLimit(chronological, normalizedLimit);
|
|
1124
|
+
if (!firstUser)
|
|
1125
|
+
return tail;
|
|
1126
|
+
const keyOf = (msg) => `${msg.role}:${msg.at}:${msg.content}`;
|
|
1127
|
+
if (tail.some((msg) => keyOf(msg) === keyOf(firstUser)))
|
|
1128
|
+
return tail;
|
|
1129
|
+
return dedupeRecentMessages([firstUser].concat(tail));
|
|
1130
|
+
};
|
|
1119
1131
|
const dedupeToolHistory = (items) => {
|
|
1120
1132
|
const seen = new Set();
|
|
1121
1133
|
const out = [];
|
|
@@ -1203,11 +1215,15 @@ const previousUserFallbackFromWorkingMemory = (query = '', workingMemory = {}) =
|
|
|
1203
1215
|
return null;
|
|
1204
1216
|
return candidate;
|
|
1205
1217
|
};
|
|
1218
|
+
const firstUserMessageFromConversation = (recentMessages = []) => [...(recentMessages || [])]
|
|
1219
|
+
.filter((msg) => /^(user|human)$/i.test(String(msg.role || '')))
|
|
1220
|
+
.sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime())[0] || null;
|
|
1206
1221
|
const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory = {} }) => {
|
|
1207
1222
|
const normalizedQuery = normalizeIntentText(query).trim();
|
|
1208
1223
|
const users = recentUserMessages(recentMessages);
|
|
1209
1224
|
const currentUser = users.find((msg) => normalizeIntentText(msg.content).trim() === normalizedQuery) || (query ? { role: 'user', content: query, at: nowIso() } : null);
|
|
1210
1225
|
const previousUser = previousUserMessageForQuery(query, recentMessages);
|
|
1226
|
+
const firstUser = firstUserMessageFromConversation(recentMessages);
|
|
1211
1227
|
const previousUserMessage = previousUser ? truncate(previousUser.content, 500) : previousUserFallbackFromWorkingMemory(query, workingMemory);
|
|
1212
1228
|
const chronological = pruneByLimit(recentMessages || [], 10).map((msg) => ({
|
|
1213
1229
|
role: msg.role,
|
|
@@ -1216,13 +1232,14 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
|
|
|
1216
1232
|
}));
|
|
1217
1233
|
const frame = cleanContextValue({
|
|
1218
1234
|
current_user_message: currentUser ? truncate(currentUser.content, 500) : truncate(query, 500),
|
|
1235
|
+
first_user_message: firstUser ? truncate(firstUser.content, 500) : null,
|
|
1219
1236
|
previous_user_message: previousUserMessage,
|
|
1220
1237
|
recent_messages_chronological: chronological,
|
|
1221
1238
|
recent_user_messages: users
|
|
1222
1239
|
.filter((msg) => normalizeIntentText(msg.content).trim() !== normalizedQuery)
|
|
1223
1240
|
.slice(0, 5)
|
|
1224
1241
|
.map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at })),
|
|
1225
|
-
instruction: 'This is the authoritative short-term conversation frame. If the user asks about
|
|
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/recent_user_messages before using vector memories, summaries, operational state, or tool history.',
|
|
1226
1243
|
});
|
|
1227
1244
|
if (!previousUserMessage)
|
|
1228
1245
|
frame.previous_user_message = null;
|
|
@@ -1234,6 +1251,7 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
|
|
|
1234
1251
|
if (!recall && !agendaStatus)
|
|
1235
1252
|
return null;
|
|
1236
1253
|
const previousUser = previousUserMessageForQuery(query, recentMessages);
|
|
1254
|
+
const firstUser = firstUserMessageFromConversation(recentMessages);
|
|
1237
1255
|
const users = recentUserMessages(recentMessages)
|
|
1238
1256
|
.filter((msg) => normalizeIntentText(msg.content).trim() !== normalizeIntentText(query).trim())
|
|
1239
1257
|
.slice(0, 3)
|
|
@@ -1243,6 +1261,7 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
|
|
|
1243
1261
|
const focus = cleanContextValue({
|
|
1244
1262
|
current_user_request: truncate(query, 500),
|
|
1245
1263
|
intent: recall ? 'conversation_recall' : 'agenda_status_question',
|
|
1264
|
+
first_user_message: firstUser ? truncate(firstUser.content, 500) : null,
|
|
1246
1265
|
previous_user_message: previousUserMessage,
|
|
1247
1266
|
recent_user_messages: users,
|
|
1248
1267
|
agenda_status: agendaStatus ? {
|
|
@@ -1260,7 +1279,7 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
|
|
|
1260
1279
|
has_availability: Boolean(agenda.has_availability),
|
|
1261
1280
|
} : undefined,
|
|
1262
1281
|
instruction: recall
|
|
1263
|
-
? 'Answer the user meta-question from previous_user_message/recent_user_messages. Do not answer with agenda availability unless the user asks for availability.'
|
|
1282
|
+
? 'Answer the user meta-question from first_user_message/previous_user_message/recent_user_messages. Do not answer with agenda availability unless the user asks for availability.'
|
|
1264
1283
|
: 'Answer whether the appointment/reservation is already scheduled using agenda_status. Do not offer availability as the main answer unless there is no schedule/reservation state.',
|
|
1265
1284
|
});
|
|
1266
1285
|
if (!previousUserMessage)
|
|
@@ -1915,8 +1934,8 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
1915
1934
|
section: 'context_header',
|
|
1916
1935
|
title: 'Tembory context',
|
|
1917
1936
|
value: compactForAgent || compactStateSections
|
|
1918
|
-
? 'Read-only memory. Conversation frame is authoritative for 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.'
|
|
1919
|
-
: '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 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.',
|
|
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.',
|
|
1920
1939
|
});
|
|
1921
1940
|
}
|
|
1922
1941
|
sections.push({
|
|
@@ -3132,6 +3151,41 @@ class Mem0Memory {
|
|
|
3132
3151
|
connectedAi.errors.push(`graph.relations: ${error.message || String(error)}`);
|
|
3133
3152
|
}
|
|
3134
3153
|
}
|
|
3154
|
+
if (connectedEmbedding && adv.includeRecentMessages !== false) {
|
|
3155
|
+
try {
|
|
3156
|
+
const transcriptProbe = [
|
|
3157
|
+
'recent conversation transcript',
|
|
3158
|
+
'first user message earliest message last user messages previous message full conversation',
|
|
3159
|
+
query,
|
|
3160
|
+
].filter(Boolean).join('\n');
|
|
3161
|
+
const transcriptBody = {
|
|
3162
|
+
user_id: key,
|
|
3163
|
+
agent_id: adv.agentId ? String(adv.agentId) : undefined,
|
|
3164
|
+
run_id: adv.runId ? String(adv.runId) : undefined,
|
|
3165
|
+
top_k: Math.max(Number(adv.recentMessagesLastN || 8) * 6, 50),
|
|
3166
|
+
filters: { kind: 'recent_message' },
|
|
3167
|
+
};
|
|
3168
|
+
const transcriptRes = await searchClientVectorMemories(this, connectedEmbedding, transcriptProbe, transcriptBody);
|
|
3169
|
+
const transcriptMemories = normalizeResults(transcriptRes).map((memory) => withTemboryScore(memory, {
|
|
3170
|
+
semanticScore: scoreOf(memory),
|
|
3171
|
+
source: 'recent_message_probe',
|
|
3172
|
+
}));
|
|
3173
|
+
if (transcriptMemories.length) {
|
|
3174
|
+
const seen = new Set(vectorMemories.map((memory) => stableHash(memoryText(memory) || safeStringify(memory))));
|
|
3175
|
+
for (const memory of transcriptMemories) {
|
|
3176
|
+
const hash = stableHash(memoryText(memory) || safeStringify(memory));
|
|
3177
|
+
if (!seen.has(hash)) {
|
|
3178
|
+
seen.add(hash);
|
|
3179
|
+
vectorMemories.push(memory);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
connectedAi.recentMessageProbe = true;
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
catch (error) {
|
|
3186
|
+
connectedAi.errors.push(`recentMessageProbe: ${error.message || String(error)}`);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3135
3189
|
let persistedRecentMessages = [];
|
|
3136
3190
|
let persistedToolHistory = [];
|
|
3137
3191
|
let persistedMemoryItems = [];
|
|
@@ -3155,14 +3209,18 @@ class Mem0Memory {
|
|
|
3155
3209
|
.filter(Boolean)
|
|
3156
3210
|
.sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
|
|
3157
3211
|
}
|
|
3212
|
+
const vectorRecentMessages = adv.includeRecentMessages === false ? [] : vectorMemories
|
|
3213
|
+
.map(recentMessageFromMemory)
|
|
3214
|
+
.filter(Boolean)
|
|
3215
|
+
.sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
|
|
3158
3216
|
if (adv.includeToolHistory !== false) {
|
|
3159
3217
|
persistedToolHistory = persistedMemoryItems
|
|
3160
3218
|
.flatMap(toolHistoryItemsFromMemory)
|
|
3161
3219
|
.sort((a, b) => new Date(a.at || 0).getTime() - new Date(b.at || 0).getTime());
|
|
3162
3220
|
}
|
|
3163
|
-
const storedRecentMessages = adv.includeRecentMessages === false ? [] : (store.recentMessages[key] || []).concat(persistedRecentMessages);
|
|
3221
|
+
const storedRecentMessages = adv.includeRecentMessages === false ? [] : (store.recentMessages[key] || []).concat(persistedRecentMessages, vectorRecentMessages);
|
|
3164
3222
|
const allRecentMessages = dedupeRecentMessages(appendCurrentUserMessage(storedRecentMessages, query));
|
|
3165
|
-
const recentMessages =
|
|
3223
|
+
const recentMessages = pruneConversationMessagesPreserveAnchors(allRecentMessages, Math.max(Number(adv.recentMessagesLastN || 8), 8));
|
|
3166
3224
|
const toolHistoryFromRecentMessages = [];
|
|
3167
3225
|
if (adv.includeToolHistory !== false) {
|
|
3168
3226
|
for (const msg of recentMessages)
|
|
@@ -3529,6 +3587,8 @@ exports.__private = {
|
|
|
3529
3587
|
toolHistoryFromMemory,
|
|
3530
3588
|
recentMessageFromMemory,
|
|
3531
3589
|
previousUserFallbackFromWorkingMemory,
|
|
3590
|
+
firstUserMessageFromConversation,
|
|
3591
|
+
pruneConversationMessagesPreserveAnchors,
|
|
3532
3592
|
dedupeToolHistory,
|
|
3533
3593
|
applyToolHistoryWindow,
|
|
3534
3594
|
dedupeRecentMessages,
|
package/package.json
CHANGED