n8n-nodes-tembory 1.1.30 → 1.1.31
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 +73 -11
- package/dist/nodes/Tembory/TemboryMemory.node.js +82 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,14 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
Node de memoria operacional da Tembory para agentes de IA no n8n.
|
|
4
4
|
|
|
5
|
-
Versao atual: `1.1.
|
|
5
|
+
Versao atual: `1.1.31`.
|
|
6
6
|
|
|
7
|
-
## 1.1.
|
|
7
|
+
## 1.1.31
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- Mantem
|
|
9
|
+
- Adiciona `agentContextBudget` no payload visual do n8n com caracteres e tokens aproximados injetados no Agent.
|
|
10
|
+
- Mostra `loadedSections` fora do bloco de diagnostico para facilitar debug humano.
|
|
11
|
+
- Melhora o parsing de outputs comuns de tools do n8n, incluindo envelopes `json`, `data`, `body` e `result`.
|
|
12
|
+
- Mantem `outputParsed`, `capturedAt`, `toolTimestamp`, `toolStatus`, conversa e save visual por execucao.
|
|
13
|
+
|
|
14
|
+
## Contrato operacional atual
|
|
15
|
+
|
|
16
|
+
O Tembory e memoria operacional generica. Ele nao decide regra de negocio, nao escolhe a tool pelo dominio do cliente e nao substitui o prompt do agente.
|
|
17
|
+
|
|
18
|
+
O que ele deve guardar e devolver:
|
|
19
|
+
|
|
20
|
+
- Mensagens recentes por `threadId` ou `sessionId`.
|
|
21
|
+
- Tool calls genericas, independente do nome da tool.
|
|
22
|
+
- Input enviado para a tool.
|
|
23
|
+
- Output bruto retornado pela tool.
|
|
24
|
+
- Output util em `outputParsed`, quando o formato permitir parsing seguro.
|
|
25
|
+
- Status da tool.
|
|
26
|
+
- `capturedAt`, que e quando o Tembory capturou o evento.
|
|
27
|
+
- `toolTimestamp`, que e o timestamp retornado pela propria tool quando existir.
|
|
28
|
+
- `actionLedger`, `workingMemory`, `decisionState`, `operationalState` e resumo ativo.
|
|
29
|
+
|
|
30
|
+
O que ele nao deve fazer:
|
|
31
|
+
|
|
32
|
+
- Inventar politica de tool.
|
|
33
|
+
- Considerar uma acao lateral concluida sem tool bem sucedida no turno correto.
|
|
34
|
+
- Transformar observacao ou inferencia em regra obrigatoria.
|
|
35
|
+
- Esconder falha de save no payload visual.
|
|
13
36
|
|
|
14
37
|
## Visao geral
|
|
15
38
|
|
|
@@ -43,13 +66,52 @@ Use a credencial `Tembory API` com a API key gerada no SaaS. A API key identific
|
|
|
43
66
|
|
|
44
67
|
O campo `Preset Operacional` preenche defaults de producao:
|
|
45
68
|
|
|
46
|
-
- `
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
- `
|
|
50
|
-
- `
|
|
69
|
+
- `Producao Rapida (Thread + SLM)`: padrao recomendado para a maioria dos agentes.
|
|
70
|
+
- `Producao Balanceada`: mais contexto, ainda compacto.
|
|
71
|
+
- `Producao Economica`: reduz resultados e secoes para economizar tokens.
|
|
72
|
+
- `Producao Nano/SLM`: otimizado para modelo pequeno.
|
|
73
|
+
- `Diagnostico Completo`: teste e auditoria.
|
|
51
74
|
- `Auditoria`: payload detalhado para depuracao.
|
|
52
75
|
|
|
76
|
+
Recomendacao pratica:
|
|
77
|
+
|
|
78
|
+
- Use `Producao Rapida` em producao normal.
|
|
79
|
+
- Use `Producao Balanceada` quando o agente precisar comparar mais historico.
|
|
80
|
+
- Use `Auditoria` ou `Diagnostico Completo` apenas enquanto estiver depurando.
|
|
81
|
+
|
|
82
|
+
## Uso com `$fromAI()`
|
|
83
|
+
|
|
84
|
+
Em tools conectadas ao AI Agent, o n8n permite que o modelo preencha parametros dinamicamente com `$fromAI()`. Isso e responsabilidade do Agent e da tool do n8n. O Tembory apenas fornece contexto operacional para o Agent, incluindo conversa, tools anteriores, inputs, outputs e timestamps.
|
|
85
|
+
|
|
86
|
+
Referencia oficial do n8n: https://docs.n8n.io/advanced-ai/examples/using-the-fromai-function/
|
|
87
|
+
|
|
88
|
+
## Payload visual do n8n
|
|
89
|
+
|
|
90
|
+
No output do node durante uma execucao, o Tembory mostra um resumo compacto para humano:
|
|
91
|
+
|
|
92
|
+
- `memorySummary.intent`: classificacao operacional leve do turno.
|
|
93
|
+
- `memorySummary.conversation`: contagem e ultimas mensagens.
|
|
94
|
+
- `memorySummary.conversationTimeline`: timeline curta com timestamps.
|
|
95
|
+
- `memorySummary.toolEvents`: ultimas tool calls com input, output, `outputParsed`, status e timestamps.
|
|
96
|
+
- `memorySummary.lastSave`: ultimo save persistido.
|
|
97
|
+
- `memorySummary.loadedSections`: quais blocos foram injetados no contexto do Agent.
|
|
98
|
+
- `memorySummary.agentContextBudget`: tamanho aproximado do contexto enviado ao Agent.
|
|
99
|
+
|
|
100
|
+
Exemplo de budget:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"agentContextBudget": {
|
|
105
|
+
"messages": 1,
|
|
106
|
+
"chars": 4200,
|
|
107
|
+
"approxTokens": 1050,
|
|
108
|
+
"largestSections": [
|
|
109
|
+
{ "section": "tools", "chars": 1600, "approxTokens": 400 }
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
53
115
|
## Blocos de contexto
|
|
54
116
|
|
|
55
117
|
Quando habilitados, o agente recebe:
|
|
@@ -2400,6 +2400,7 @@ const normalizeToolResultEnvelope = (value) => {
|
|
|
2400
2400
|
}
|
|
2401
2401
|
if (!parsed || typeof parsed !== 'object')
|
|
2402
2402
|
return parsed;
|
|
2403
|
+
const keys = Object.keys(parsed);
|
|
2403
2404
|
if (typeof parsed.response === 'string') {
|
|
2404
2405
|
const nested = normalizeToolResultEnvelope(parsed.response);
|
|
2405
2406
|
if (nested !== undefined)
|
|
@@ -2407,6 +2408,12 @@ const normalizeToolResultEnvelope = (value) => {
|
|
|
2407
2408
|
}
|
|
2408
2409
|
if (parsed.response && typeof parsed.response === 'object')
|
|
2409
2410
|
return normalizeToolResultEnvelope(parsed.response);
|
|
2411
|
+
if (parsed.json !== undefined && keys.every((key) => ['json', 'pairedItem', 'pairedItems'].includes(key)))
|
|
2412
|
+
return normalizeToolResultEnvelope(parsed.json);
|
|
2413
|
+
for (const key of ['data', 'body', 'result', 'results', 'value']) {
|
|
2414
|
+
if (parsed[key] !== undefined && keys.length === 1)
|
|
2415
|
+
return normalizeToolResultEnvelope(parsed[key]);
|
|
2416
|
+
}
|
|
2410
2417
|
return parsed;
|
|
2411
2418
|
};
|
|
2412
2419
|
const compactParsedToolOutputForSideChannel = (parsed) => {
|
|
@@ -2418,6 +2425,13 @@ const compactParsedToolOutputForSideChannel = (parsed) => {
|
|
|
2418
2425
|
return parsed;
|
|
2419
2426
|
if (parsed.output !== undefined)
|
|
2420
2427
|
return cleanContextValue(stripNoisyToolFields(parsed.output));
|
|
2428
|
+
const keys = Object.keys(parsed);
|
|
2429
|
+
if (parsed.json !== undefined && keys.every((key) => ['json', 'pairedItem', 'pairedItems'].includes(key)))
|
|
2430
|
+
return compactParsedToolOutputForSideChannel(parsed.json);
|
|
2431
|
+
for (const key of ['data', 'body', 'result', 'results', 'value']) {
|
|
2432
|
+
if (parsed[key] !== undefined && keys.length === 1)
|
|
2433
|
+
return compactParsedToolOutputForSideChannel(parsed[key]);
|
|
2434
|
+
}
|
|
2421
2435
|
return cleanContextValue(stripNoisyToolFields(parsed));
|
|
2422
2436
|
};
|
|
2423
2437
|
const toolResultTimestampFromParsed = (parsed) => {
|
|
@@ -2838,6 +2852,69 @@ const loadedSectionsForSideChannel = (parsed = {}, memoryAudit = {}) => cleanCon
|
|
|
2838
2852
|
recentHighlights: Array.isArray(parsed.recentHighlights),
|
|
2839
2853
|
contextHealth: Boolean(parsed.contextHealth),
|
|
2840
2854
|
});
|
|
2855
|
+
const budgetEntryForSideChannel = (section, value) => {
|
|
2856
|
+
if (value === undefined || value === null)
|
|
2857
|
+
return undefined;
|
|
2858
|
+
const text = typeof value === 'string' ? value : safeStringify(value);
|
|
2859
|
+
if (!text)
|
|
2860
|
+
return undefined;
|
|
2861
|
+
return {
|
|
2862
|
+
section,
|
|
2863
|
+
chars: text.length,
|
|
2864
|
+
approxTokens: approxTokenCount(text),
|
|
2865
|
+
};
|
|
2866
|
+
};
|
|
2867
|
+
const agentContextBudgetForSideChannel = (messages = [], parsed = {}) => {
|
|
2868
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
2869
|
+
const messageBudgets = list.map((message, index) => {
|
|
2870
|
+
const content = messageContentOf(message);
|
|
2871
|
+
const role = messageTypeOf(message) || message.role || 'message';
|
|
2872
|
+
return {
|
|
2873
|
+
index,
|
|
2874
|
+
role,
|
|
2875
|
+
chars: content.length,
|
|
2876
|
+
approxTokens: approxTokenCount(content),
|
|
2877
|
+
};
|
|
2878
|
+
});
|
|
2879
|
+
const totalChars = messageBudgets.reduce((sum, item) => sum + item.chars, 0);
|
|
2880
|
+
const sectionCandidates = [
|
|
2881
|
+
['instruction', parsed.instruction],
|
|
2882
|
+
['conversation', parsed.conversation],
|
|
2883
|
+
['current_turn_focus', parsed.current_turn_focus || parsed.currentTurn || parsed.current_turn],
|
|
2884
|
+
['action_directive', parsed.action_directive],
|
|
2885
|
+
['turn_brief', parsed.turnBrief || parsed.turn_brief],
|
|
2886
|
+
['summary', parsed.summary],
|
|
2887
|
+
['working_memory', parsed.workingMemory || parsed.working_memory],
|
|
2888
|
+
['decision_state', parsed.decisionState || parsed.decision_state],
|
|
2889
|
+
['operational_state', parsed.operationalState || parsed.operational_state],
|
|
2890
|
+
['tools', parsed.tools],
|
|
2891
|
+
['action_ledger', parsed.actionLedger || parsed.action_ledger],
|
|
2892
|
+
['memory_compression', parsed.memoryCompression || parsed.memory_compression],
|
|
2893
|
+
['profile', parsed.profile || parsed.profileFacts || parsed.profile_facts],
|
|
2894
|
+
['memory_audit', parsed.memoryAudit || parsed.memory_audit],
|
|
2895
|
+
['dedupe', parsed.dedupeSummary || parsed.dedupe_summary],
|
|
2896
|
+
];
|
|
2897
|
+
const sections = sectionCandidates
|
|
2898
|
+
.map(([section, value]) => budgetEntryForSideChannel(section, value))
|
|
2899
|
+
.filter(Boolean)
|
|
2900
|
+
.sort((a, b) => b.approxTokens - a.approxTokens);
|
|
2901
|
+
const approxTokens = approxTokenCount(list.map((message) => messageContentOf(message)).join('\n'));
|
|
2902
|
+
const warnings = [];
|
|
2903
|
+
if (approxTokens > 12000)
|
|
2904
|
+
warnings.push('agent context above 12k approximate tokens');
|
|
2905
|
+
else if (approxTokens > 8000)
|
|
2906
|
+
warnings.push('agent context above 8k approximate tokens');
|
|
2907
|
+
if (sections[0] && sections[0].approxTokens > Math.max(2000, approxTokens * 0.45))
|
|
2908
|
+
warnings.push(`largest section is ${sections[0].section}`);
|
|
2909
|
+
return cleanContextValue({
|
|
2910
|
+
messages: list.length,
|
|
2911
|
+
chars: totalChars,
|
|
2912
|
+
approxTokens,
|
|
2913
|
+
byMessage: messageBudgets.slice(0, 8),
|
|
2914
|
+
largestSections: sections.slice(0, 8),
|
|
2915
|
+
warnings,
|
|
2916
|
+
});
|
|
2917
|
+
};
|
|
2841
2918
|
const summarizeMemoryMessagesForSideChannel = (messages = []) => {
|
|
2842
2919
|
const list = Array.isArray(messages) ? messages : [];
|
|
2843
2920
|
const summary = { messages: list.length };
|
|
@@ -2866,6 +2943,7 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
|
|
|
2866
2943
|
const lastSave = compactLastSaveForSideChannel(memoryAudit.last_save || parsed.state?.last_save || {});
|
|
2867
2944
|
const fullDedupeSummary = parsed.dedupeSummary || parsed.diagnostics?.dedupeSummary || undefined;
|
|
2868
2945
|
const loadedSections = loadedSectionsForSideChannel(parsed, memoryAudit);
|
|
2946
|
+
const agentContextBudget = agentContextBudgetForSideChannel(list, parsed);
|
|
2869
2947
|
summary.userId = parsed.userId;
|
|
2870
2948
|
summary.project = parsed.project || undefined;
|
|
2871
2949
|
summary.retrievalMode = parsed.retrievalMode;
|
|
@@ -2921,6 +2999,8 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
|
|
|
2921
2999
|
graph: Array.isArray(parsed.graph) ? parsed.graph.length : undefined,
|
|
2922
3000
|
recentHighlights: Array.isArray(parsed.recentHighlights) ? parsed.recentHighlights.length : undefined,
|
|
2923
3001
|
});
|
|
3002
|
+
summary.loadedSections = loadedSections;
|
|
3003
|
+
summary.agentContextBudget = agentContextBudget;
|
|
2924
3004
|
summary.lastSave = Object.keys(lastSave).length ? lastSave : undefined;
|
|
2925
3005
|
summary.dedupe = fullDedupeSummary ? compactDedupeForSideChannel(fullDedupeSummary) : undefined;
|
|
2926
3006
|
summary.workingMemory = cleanContextValue({
|
|
@@ -2959,11 +3039,11 @@ const summarizeMemoryMessagesForSideChannel = (messages = []) => {
|
|
|
2959
3039
|
};
|
|
2960
3040
|
const contextSizeOfMessages = (messages = []) => {
|
|
2961
3041
|
const perMessage = (messages || []).map((message, index) => {
|
|
2962
|
-
const content =
|
|
3042
|
+
const content = messageContentOf(message);
|
|
2963
3043
|
return { index, role: message.role || 'system', chars: content.length, approx_tokens: approxTokenCount(content) };
|
|
2964
3044
|
});
|
|
2965
3045
|
const chars = perMessage.reduce((sum, item) => sum + item.chars, 0);
|
|
2966
|
-
return { chars, approx_tokens: approxTokenCount((messages || []).map((m) => m
|
|
3046
|
+
return { chars, approx_tokens: approxTokenCount((messages || []).map((m) => messageContentOf(m)).join('\n')), messages: perMessage };
|
|
2967
3047
|
};
|
|
2968
3048
|
const wrapTemboryMemory = (memory, ctx, memoryKey, itemIndex = 0) => new Proxy(memory, {
|
|
2969
3049
|
get(target, prop) {
|
package/package.json
CHANGED