Found while syncing this cycle's fixes to the live DEV workflow and
preparing Run A: tools/build-workflow.js's readLogicSource() only inlines
whatever a node's own `@requires` header lists -- it does not resolve
transitive dependencies. context.js started doing `require('./normalize')`
earlier this cycle, but the three node drivers that need normalizeTicket/
buildAiContextText (which call normalizeGlpiText internally) still only
declared `@requires context.js`, never `normalize.js` itself.
The result compiles fine (new Function(...) syntax-checks pass -- a
ReferenceError is a runtime error, not a syntax error) but would throw
"normalizeGlpiText is not defined" the moment any of these three nodes
actually ran:
- Code - Mesclar Contexto e Calcular Hash (merge-context-hash.node.js)
- Code - Normalizar Candidatos Base (normalize-candidates.node.js)
- Code - Preparar Prompt IA (prepare-ai-prompt.node.js)
This never surfaced in Run 1 (prior session) because normalize.js/the
descricao field didn't exist yet at that point, and it didn't surface in
this cycle's local test suite because node--test only exercises the pure
logic/*.js modules directly (with real requires), never the inlined,
concatenated jsCode a node actually runs in n8n.
Fixed by adding normalize.js explicitly to each of the three headers,
matching the codebase's existing convention for multi-file deps (e.g.
compute-score.node.js already lists `@requires cooldown.js score.js`
together instead of relying on any transitive resolution).
Added a regression test that scans every generated Code node for calls to
normalizeGlpiText/normalizeTicket/buildAiContextText and asserts the
normalizeGlpiText function definition is actually present in that same
inlined source -- would have caught this before it ever reached DEV.
102/102 tests passing (was 101/101 -- +1 new regression test).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
58 lines
2.9 KiB
JavaScript
58 lines
2.9 KiB
JavaScript
// @requires normalize.js context.js
|
|
const VALID_CLASSIFICATIONS = [
|
|
'ACTIONABLE_NOW', 'UNASSIGNED_ACTIONABLE', 'WAITING_REQUESTER', 'WAITING_VENDOR',
|
|
'WAITING_APPROVAL', 'SCHEDULED', 'DEPENDENCY_BLOCKED', 'NEEDS_COORDINATION',
|
|
'PROJECT_LONG_TERM', 'DUPLICATE_OR_OBSOLETE', 'INSUFFICIENT_CONTEXT',
|
|
];
|
|
|
|
const systemPrompt = [
|
|
'Voce e um analisador operacional de chamados de TI (GLPI). Voce NAO e um atendente e NAO conversa com o usuario.',
|
|
'Use SOMENTE as informacoes fornecidas abaixo sobre o chamado. Nunca invente fatos.',
|
|
'Distinga evidencia (o que esta escrito no chamado) de inferencia (sua conclusao).',
|
|
'SLA vencido sozinho NAO e evidencia de que existe uma acao executavel agora.',
|
|
'Idade do chamado sozinha NAO e evidencia de acionabilidade.',
|
|
'Identifique: existe acao executavel hoje? quem pode executar? existe bloqueio? quem segura o bloqueio? qual a proxima acao concreta? existe data conhecida para revisao? existe evidencia nos apontamentos que justifique a conclusao?',
|
|
'Se a informacao for insuficiente para decidir com confianca, use a classificacao INSUFFICIENT_CONTEXT e confidence baixo -- nao invente uma acao.',
|
|
`Responda em JSON estrito com os campos: classification (uma de: ${VALID_CLASSIFICATIONS.join(', ')}), actionable_today (boolean), confidence (0 a 1), blocker_type, waiting_for, next_action (curto, poucas palavras, sem paragrafo), suggested_action_type, business_risk (low|medium|high), reason (uma frase), evidence (lista curta de evidencias textuais encontradas).`,
|
|
].join('\n');
|
|
|
|
return $input.all().map((item) => {
|
|
const data = item.json;
|
|
const userPrompt = buildAiContextText(data.ticket, data.config.aiMaxContextChars);
|
|
|
|
const requestBody = {
|
|
model: data.config.aiModel,
|
|
temperature: 0.1,
|
|
response_format: {
|
|
type: 'json_schema',
|
|
json_schema: {
|
|
name: 'ticket_classification',
|
|
strict: true,
|
|
schema: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
classification: { type: 'string', enum: VALID_CLASSIFICATIONS },
|
|
actionable_today: { type: 'boolean' },
|
|
confidence: { type: 'number' },
|
|
blocker_type: { type: ['string', 'null'] },
|
|
waiting_for: { type: ['string', 'null'] },
|
|
next_action: { type: 'string' },
|
|
suggested_action_type: { type: 'string' },
|
|
business_risk: { type: 'string' },
|
|
reason: { type: 'string' },
|
|
evidence: { type: 'array', items: { type: 'string' } },
|
|
},
|
|
required: ['classification', 'actionable_today', 'confidence', 'blocker_type', 'waiting_for', 'next_action', 'suggested_action_type', 'business_risk', 'reason', 'evidence'],
|
|
},
|
|
},
|
|
},
|
|
messages: [
|
|
{ role: 'system', content: systemPrompt },
|
|
{ role: 'user', content: userPrompt },
|
|
],
|
|
};
|
|
|
|
return { json: { ...data, requestBody } };
|
|
});
|