n8n-nodes-tembory 1.1.39 → 1.1.40
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 +7 -1
- package/dist/nodes/Tembory/TemboryMemory.node.js +165 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
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.40`.
|
|
6
|
+
|
|
7
|
+
## 1.1.40
|
|
8
|
+
|
|
9
|
+
- Extrai fatos operacionais path aware de inputs e outputs de tools, preservando IDs como `serviceId`, `providerId`, `locationId`, `customerId`, `reservationId` e `bookingId`.
|
|
10
|
+
- Injeta esses fatos em `operationalState`, `actionLedger`, `toolLedger` e `action_directive`.
|
|
11
|
+
- Corrige o risco de o Agent usar `serviceId` antigo de memoria vetorial quando o `check_availabilities` mais recente ja retornou o ID correto para o `Booking`.
|
|
6
12
|
|
|
7
13
|
## 1.1.39
|
|
8
14
|
|
|
@@ -489,6 +489,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
489
489
|
const name = String(tool.name || 'unknown_tool');
|
|
490
490
|
const compactInput = compactToolPayload(safeParseToolPayload(tool.input));
|
|
491
491
|
const compactResult = maybeToolResult(tool, includeResults);
|
|
492
|
+
const facts = extractToolOperationalFacts(tool);
|
|
492
493
|
const reason = truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260);
|
|
493
494
|
toolCountsByName[name] = (toolCountsByName[name] || 0) + 1;
|
|
494
495
|
latestByName[name] = {
|
|
@@ -496,6 +497,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
496
497
|
ok: tool.ok !== false,
|
|
497
498
|
at: tool.at || null,
|
|
498
499
|
input: compactInput,
|
|
500
|
+
facts,
|
|
499
501
|
result: compactResult,
|
|
500
502
|
reason,
|
|
501
503
|
source: tool.source || 'unknown',
|
|
@@ -507,6 +509,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
507
509
|
at: tool.at || null,
|
|
508
510
|
reason,
|
|
509
511
|
input: compactInput,
|
|
512
|
+
facts,
|
|
510
513
|
output: compactResult,
|
|
511
514
|
}));
|
|
512
515
|
if (tool.ok === false)
|
|
@@ -538,6 +541,7 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
|
|
|
538
541
|
last_successful_tool: successfulTools.length ? {
|
|
539
542
|
name: successfulTools[successfulTools.length - 1].name,
|
|
540
543
|
at: successfulTools[successfulTools.length - 1].at || null,
|
|
544
|
+
facts: extractToolOperationalFacts(successfulTools[successfulTools.length - 1]),
|
|
541
545
|
result: maybeToolResult(successfulTools[successfulTools.length - 1], includeResults),
|
|
542
546
|
} : null,
|
|
543
547
|
},
|
|
@@ -556,6 +560,7 @@ const deriveActionLedger = (toolHistory = [], maxItems = 20, includeResults = tr
|
|
|
556
560
|
status: tool.ok === false ? 'failed' : 'ok',
|
|
557
561
|
reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260),
|
|
558
562
|
input: compactToolPayload(safeParseToolPayload(tool.input)),
|
|
563
|
+
facts: extractToolOperationalFacts(tool),
|
|
559
564
|
result: maybeToolResult(tool, includeResults),
|
|
560
565
|
at: tool.at || null,
|
|
561
566
|
source: tool.source || 'unknown',
|
|
@@ -2094,16 +2099,25 @@ const buildActionDirective = ({ workingMemory = {}, operationalState = {} }) =>
|
|
|
2094
2099
|
return null;
|
|
2095
2100
|
const toolState = (operationalState || {}).tool_state || {};
|
|
2096
2101
|
const latestForTool = ((toolState.latest_by_name || {})[tool]) || null;
|
|
2102
|
+
const recentToolEvidence = pruneByLimit((toolState.recent_sequence || [])
|
|
2103
|
+
.filter((item) => item && item.status !== 'failed')
|
|
2104
|
+
.map((item) => cleanContextValue({
|
|
2105
|
+
tool: item.tool,
|
|
2106
|
+
at: item.at,
|
|
2107
|
+
facts: item.facts,
|
|
2108
|
+
}))
|
|
2109
|
+
.filter((item) => item && item.facts), 6);
|
|
2097
2110
|
return cleanContextValue({
|
|
2098
2111
|
required_tool: tool,
|
|
2099
2112
|
next_expected_action: next,
|
|
2113
|
+
evidence_from_recent_tools: recentToolEvidence,
|
|
2100
2114
|
tool_state: {
|
|
2101
2115
|
last_tool: (operationalState || {}).last_tool || null,
|
|
2102
2116
|
required_tool_last_result: latestForTool,
|
|
2103
2117
|
counts_by_name: toolState.counts_by_name || {},
|
|
2104
2118
|
failed_by_name: toolState.failed_by_name || {},
|
|
2105
2119
|
},
|
|
2106
|
-
instruction: `
|
|
2120
|
+
instruction: `call ${tool} now if the agent prompt requires it. Use tool evidence for exact IDs. Do not complete side effects from memory.`,
|
|
2107
2121
|
});
|
|
2108
2122
|
};
|
|
2109
2123
|
const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
|
|
@@ -2520,6 +2534,152 @@ const normalizeToolResultEnvelope = (value) => {
|
|
|
2520
2534
|
}
|
|
2521
2535
|
return parsed;
|
|
2522
2536
|
};
|
|
2537
|
+
const normalizeFactKey = (key = '') => String(key || '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
2538
|
+
const scalarFactValue = (value) => {
|
|
2539
|
+
if (value === undefined || value === null || value === '')
|
|
2540
|
+
return undefined;
|
|
2541
|
+
if (['string', 'number', 'boolean'].includes(typeof value))
|
|
2542
|
+
return value;
|
|
2543
|
+
return undefined;
|
|
2544
|
+
};
|
|
2545
|
+
const pushCandidate = (facts, bucket, value, path) => {
|
|
2546
|
+
const scalar = scalarFactValue(value);
|
|
2547
|
+
if (scalar === undefined)
|
|
2548
|
+
return;
|
|
2549
|
+
facts[bucket] = facts[bucket] || [];
|
|
2550
|
+
if (!facts[bucket].some((item) => String(item.value) === String(scalar) && item.path === path)) {
|
|
2551
|
+
facts[bucket].push({ value: scalar, path });
|
|
2552
|
+
}
|
|
2553
|
+
};
|
|
2554
|
+
const firstCandidateValue = (items = []) => items.length ? items[0].value : undefined;
|
|
2555
|
+
const extractOperationalFactsFromValue = (value, maxCandidates = 8) => {
|
|
2556
|
+
const parsed = normalizeToolResultEnvelope(value);
|
|
2557
|
+
const facts = {
|
|
2558
|
+
serviceIdCandidates: [],
|
|
2559
|
+
providerIdCandidates: [],
|
|
2560
|
+
locationIdCandidates: [],
|
|
2561
|
+
customerIdCandidates: [],
|
|
2562
|
+
reservationIdCandidates: [],
|
|
2563
|
+
bookingIdCandidates: [],
|
|
2564
|
+
confirmationIdCandidates: [],
|
|
2565
|
+
serviceNameCandidates: [],
|
|
2566
|
+
providerNameCandidates: [],
|
|
2567
|
+
statusCandidates: [],
|
|
2568
|
+
timeCandidates: [],
|
|
2569
|
+
dateCandidates: [],
|
|
2570
|
+
};
|
|
2571
|
+
const seen = new Set();
|
|
2572
|
+
let visited = 0;
|
|
2573
|
+
const visit = (item, path = []) => {
|
|
2574
|
+
if (visited > 220 || item === undefined || item === null)
|
|
2575
|
+
return;
|
|
2576
|
+
if (typeof item !== 'object')
|
|
2577
|
+
return;
|
|
2578
|
+
if (seen.has(item))
|
|
2579
|
+
return;
|
|
2580
|
+
seen.add(item);
|
|
2581
|
+
visited += 1;
|
|
2582
|
+
if (Array.isArray(item)) {
|
|
2583
|
+
item.slice(0, 12).forEach((entry, index) => visit(entry, path.concat(String(index))));
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
for (const [rawKey, rawValue] of Object.entries(item)) {
|
|
2587
|
+
const key = normalizeFactKey(rawKey);
|
|
2588
|
+
const nextPath = path.concat(rawKey);
|
|
2589
|
+
const pathText = nextPath.join('.');
|
|
2590
|
+
const parentText = path.join('.').toLowerCase();
|
|
2591
|
+
const parentSuggestsService = /(^|\.)(service|services|servico|servicos|especialidade|especialidades)(\.|$)/i.test(parentText);
|
|
2592
|
+
const parentSuggestsProvider = /(^|\.)(provider|providers|veterinario|veterinarios|vet|vets|professional|profissional|profissionais)(\.|$)/i.test(parentText);
|
|
2593
|
+
const parentSuggestsLocation = /(^|\.)(location|locations|local|locais|unidade|unidades)(\.|$)/i.test(parentText);
|
|
2594
|
+
if (key === 'serviceid' || key === 'servicoid' || (key === 'id' && parentSuggestsService))
|
|
2595
|
+
pushCandidate(facts, 'serviceIdCandidates', rawValue, pathText);
|
|
2596
|
+
if (key === 'providerid' || key === 'veterinarioid' || key === 'vetid' || (key === 'id' && parentSuggestsProvider))
|
|
2597
|
+
pushCandidate(facts, 'providerIdCandidates', rawValue, pathText);
|
|
2598
|
+
if (key === 'locationid' || key === 'localid' || key === 'unidadeid' || (key === 'id' && parentSuggestsLocation))
|
|
2599
|
+
pushCandidate(facts, 'locationIdCandidates', rawValue, pathText);
|
|
2600
|
+
if (key === 'customerid' || key === 'clienteid')
|
|
2601
|
+
pushCandidate(facts, 'customerIdCandidates', rawValue, pathText);
|
|
2602
|
+
if (key === 'reservationid' || key === 'reservaid')
|
|
2603
|
+
pushCandidate(facts, 'reservationIdCandidates', rawValue, pathText);
|
|
2604
|
+
if (key === 'bookingid' || key === 'appointmentid' || key === 'agendamentoid')
|
|
2605
|
+
pushCandidate(facts, 'bookingIdCandidates', rawValue, pathText);
|
|
2606
|
+
if (key === 'confirmationid')
|
|
2607
|
+
pushCandidate(facts, 'confirmationIdCandidates', rawValue, pathText);
|
|
2608
|
+
if (key === 'servicename' || key === 'servico' || key === 'especialidade' || (key === 'name' && parentSuggestsService))
|
|
2609
|
+
pushCandidate(facts, 'serviceNameCandidates', rawValue, pathText);
|
|
2610
|
+
if (key === 'providername' || key === 'veterinarioname' || key === 'nomeveterinario' || (key === 'name' && parentSuggestsProvider))
|
|
2611
|
+
pushCandidate(facts, 'providerNameCandidates', rawValue, pathText);
|
|
2612
|
+
if (key === 'status' || key === 'state')
|
|
2613
|
+
pushCandidate(facts, 'statusCandidates', rawValue, pathText);
|
|
2614
|
+
if (key === 'horario' || key === 'horarios' || key === 'time' || key === 'start' || key === 'end')
|
|
2615
|
+
pushCandidate(facts, 'timeCandidates', rawValue, pathText);
|
|
2616
|
+
if (key === 'date' || key === 'data' || key === 'datadisponivel')
|
|
2617
|
+
pushCandidate(facts, 'dateCandidates', rawValue, pathText);
|
|
2618
|
+
if (rawValue && typeof rawValue === 'object')
|
|
2619
|
+
visit(rawValue, nextPath);
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
visit(parsed);
|
|
2623
|
+
const compactCandidates = (items = []) => items.slice(0, maxCandidates);
|
|
2624
|
+
return cleanContextValue({
|
|
2625
|
+
ids: {
|
|
2626
|
+
serviceId: firstCandidateValue(facts.serviceIdCandidates),
|
|
2627
|
+
providerId: firstCandidateValue(facts.providerIdCandidates),
|
|
2628
|
+
locationId: firstCandidateValue(facts.locationIdCandidates),
|
|
2629
|
+
customerId: firstCandidateValue(facts.customerIdCandidates),
|
|
2630
|
+
reservationId: firstCandidateValue(facts.reservationIdCandidates),
|
|
2631
|
+
bookingId: firstCandidateValue(facts.bookingIdCandidates),
|
|
2632
|
+
confirmationId: firstCandidateValue(facts.confirmationIdCandidates),
|
|
2633
|
+
},
|
|
2634
|
+
labels: {
|
|
2635
|
+
serviceName: firstCandidateValue(facts.serviceNameCandidates),
|
|
2636
|
+
providerName: firstCandidateValue(facts.providerNameCandidates),
|
|
2637
|
+
status: firstCandidateValue(facts.statusCandidates),
|
|
2638
|
+
date: firstCandidateValue(facts.dateCandidates),
|
|
2639
|
+
time: firstCandidateValue(facts.timeCandidates),
|
|
2640
|
+
},
|
|
2641
|
+
candidates: {
|
|
2642
|
+
serviceIds: compactCandidates(facts.serviceIdCandidates),
|
|
2643
|
+
providerIds: compactCandidates(facts.providerIdCandidates),
|
|
2644
|
+
locationIds: compactCandidates(facts.locationIdCandidates),
|
|
2645
|
+
},
|
|
2646
|
+
});
|
|
2647
|
+
};
|
|
2648
|
+
const mergeOperationalFactObjects = (primary = {}, secondary = {}) => {
|
|
2649
|
+
const pick = (path) => {
|
|
2650
|
+
const read = (source) => path.reduce((acc, key) => acc && acc[key] !== undefined ? acc[key] : undefined, source);
|
|
2651
|
+
return read(primary) !== undefined ? read(primary) : read(secondary);
|
|
2652
|
+
};
|
|
2653
|
+
return cleanContextValue({
|
|
2654
|
+
ids: {
|
|
2655
|
+
serviceId: pick(['ids', 'serviceId']),
|
|
2656
|
+
providerId: pick(['ids', 'providerId']),
|
|
2657
|
+
locationId: pick(['ids', 'locationId']),
|
|
2658
|
+
customerId: pick(['ids', 'customerId']),
|
|
2659
|
+
reservationId: pick(['ids', 'reservationId']),
|
|
2660
|
+
bookingId: pick(['ids', 'bookingId']),
|
|
2661
|
+
confirmationId: pick(['ids', 'confirmationId']),
|
|
2662
|
+
},
|
|
2663
|
+
labels: {
|
|
2664
|
+
serviceName: pick(['labels', 'serviceName']),
|
|
2665
|
+
providerName: pick(['labels', 'providerName']),
|
|
2666
|
+
status: pick(['labels', 'status']),
|
|
2667
|
+
date: pick(['labels', 'date']),
|
|
2668
|
+
time: pick(['labels', 'time']),
|
|
2669
|
+
},
|
|
2670
|
+
candidates: {
|
|
2671
|
+
serviceIds: ((primary.candidates || {}).serviceIds || (secondary.candidates || {}).serviceIds || []).slice(0, 8),
|
|
2672
|
+
providerIds: ((primary.candidates || {}).providerIds || (secondary.candidates || {}).providerIds || []).slice(0, 8),
|
|
2673
|
+
locationIds: ((primary.candidates || {}).locationIds || (secondary.candidates || {}).locationIds || []).slice(0, 8),
|
|
2674
|
+
},
|
|
2675
|
+
});
|
|
2676
|
+
};
|
|
2677
|
+
const extractToolOperationalFacts = (tool = {}) => {
|
|
2678
|
+
const inputFacts = extractOperationalFactsFromValue(safeParseToolPayload(tool.input));
|
|
2679
|
+
const outputFacts = extractOperationalFactsFromValue(tool.result);
|
|
2680
|
+
const facts = mergeOperationalFactObjects(outputFacts, inputFacts);
|
|
2681
|
+
return Object.keys(facts || {}).length ? facts : undefined;
|
|
2682
|
+
};
|
|
2523
2683
|
const compactParsedToolOutputForSideChannel = (parsed) => {
|
|
2524
2684
|
if (parsed === undefined || parsed === null)
|
|
2525
2685
|
return undefined;
|
|
@@ -2559,6 +2719,7 @@ const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResul
|
|
|
2559
2719
|
at: tool.at,
|
|
2560
2720
|
reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 140),
|
|
2561
2721
|
input: truncate(String(tool.input || ''), 500) || undefined,
|
|
2722
|
+
facts: extractToolOperationalFacts(tool),
|
|
2562
2723
|
result: includeResults ? compactToolResult(tool.result, 1200) : undefined,
|
|
2563
2724
|
}));
|
|
2564
2725
|
const cleanContextValue = (value) => {
|
|
@@ -2641,7 +2802,7 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
|
|
|
2641
2802
|
const compactOperationalStateForAgent = (state = {}) => {
|
|
2642
2803
|
const counts = state.tool_counts || {};
|
|
2643
2804
|
const toolState = state.tool_state || {};
|
|
2644
|
-
return {
|
|
2805
|
+
return cleanContextValue({
|
|
2645
2806
|
last_tool: state.last_tool || null,
|
|
2646
2807
|
tool_counts: {
|
|
2647
2808
|
total: counts.total || 0,
|
|
@@ -2655,8 +2816,7 @@ const compactOperationalStateForAgent = (state = {}) => {
|
|
|
2655
2816
|
last_successful_tool: toolState.last_successful_tool || undefined,
|
|
2656
2817
|
} : undefined,
|
|
2657
2818
|
blocked_without_context: state.blocked_without_context || [],
|
|
2658
|
-
|
|
2659
|
-
};
|
|
2819
|
+
});
|
|
2660
2820
|
};
|
|
2661
2821
|
const compactMemoryCompressionForAgent = (compression = {}) => ({
|
|
2662
2822
|
turn_summary: (compression.turn_summary || []).slice(-3),
|
|
@@ -3428,7 +3588,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
|
|
|
3428
3588
|
statusAnswerMaterial: sectionValue('status_answer_material'),
|
|
3429
3589
|
action_directive: directive ? cleanContextValue({
|
|
3430
3590
|
required_tool: directive.required_tool,
|
|
3431
|
-
|
|
3591
|
+
evidence_from_recent_tools: directive.evidence_from_recent_tools,
|
|
3432
3592
|
instruction: directive.instruction,
|
|
3433
3593
|
}) : undefined,
|
|
3434
3594
|
turnBrief: compactTurnBriefForAgent(sectionValue('turn_brief')),
|
package/package.json
CHANGED