71796390e31cb486ca17c6fc7bbea59675938abe
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
482b7ef48e |
fix(v2): redefine AI actionability semantics + version the classifier (AI_CLASSIFIER_VERSION)
Production-readiness cycle. Classifier was excessively conservative (real Run A: ACTIONABLE_NOW/UNASSIGNED_ACTIONABLE both 0) and made concrete semantic mistakes on real tickets: - #34419: technician "Rafael Brito" (internal) classified as WAITING_VENDOR waiting_for -- an internal technician is not a vendor. - #37389: ticket had concrete open tasks ("Realizar um inventário das máquinas", "Alinhar o dia...") but was classified INSUFFICIENT_CONTEXT. - Generic next_action ("Revisar chamado") repeated across unrelated tickets, proving nothing about real actionability. Rewrote the system prompt (n8n/nodes/prepare-ai-prompt.node.js) with: practical actionability definition ("suporte consegue fazer algo util agora", explicit inclusion list); contatar-vs-aguardar distinction with examples; WAITING_* requires positive evidence (action already taken AND external dependency still open); technician-vs-vendor distinction citing the TECNICO context field; STATUS Planejado alone != blocked/scheduled; SCHEDULED requires an explicit date/window; mandatory chronological interpretation (most recent apontamento wins over a superseded old blocker); next_action must be concrete, with explicit accepted/rejected examples; INSUFFICIENT_CONTEXT bar raised (ask "is there a diagnostic step available even without full info" first). n8n/nodes/prepare-ai-prompt.node.test.js: 10 new tests asserting each rule's text is still present in the prompt (deterministic proxy -- behavioral proof comes from the real DEV run, inspected manually). logic/classification.js: added AI_CLASSIFIER_VERSION=3 (stamped on every valid classification); added a deterministic safety net (not just a prompt hope) -- isWaitingForInternalTechnician() rejects WAITING_VENDOR when waiting_for matches the ticket's own TECNICO field, routing to a new fallback_reason 'ai_semantic_conflict' instead of accepting a known-wrong semantic classification; added next_action genericness rejection (isGenericNextAction) for ACTIONABLE_NOW/UNASSIGNED_ACTIONABLE only. n8n/nodes/validate-ai-response.node.js: now passes the ticket into validateAiClassification() so the technician guard has the facts it needs. logic/cache.js: added buildCacheKey(contextHash, classifierVersion) -> "<hash>:v<version>", stored in the same last_context_hash column (no schema migration) -- bumping AI_CLASSIFIER_VERSION now forces a cache miss on every ticket without manually clearing the Data Table. n8n/nodes/decide-needs-ai.node.js / prepare-memoria-analise-record.node.js updated to compute/persist the composite key. logic/config.js: AI_MAX_CANDIDATES default raised 30->60 (now equals CANDIDATE_POOL_SIZE) -- no candidate should go unanalyzed by an artificial cap now that Run B already proved the cache makes repeat runs cheap; AI_FALLBACK stays reserved for real provider failures. Also added SEND_ENABLED (default false) and ENVIRONMENT config fields for the real Slack send gate (used by logic/slack.js, next commit). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
ea105d525c |
fix(v2): three real bugs found by an actual end-to-end run against production GLPI data
Ran the deployed workflow for real (temporary webhook trigger, briefly
activated, immediately deactivated and removed after each attempt) against
the real GLPI MySQL database and a real OpenAI credential. Found and fixed:
1. $env access is blocked on this n8n instance (N8N_BLOCK_ENV_ACCESS_IN_NODE).
parseConfig($env) threw "access to env vars denied" on the very first
Code node. Config now comes from the "Config - Coordenador GLPI v2" Set
node instead (same pattern the V1 workflow already used) -- expanded
that node with all 13 tunable parameters, and pointed the 3 affected
node drivers (build-sql-candidatos, decide-needs-ai, select-top10) at
it instead of $env.
2. Data Table "upsert" operation requires both `columns` (values) and a
separate `filters` (match condition) -- I had only set columns with
matchingColumns, which isn't enough; n8n refused to activate the
workflow ("Missing or invalid required parameters: filters"). Added
filters.conditions matching on ticket_id to both memory-writing Data
Table nodes.
3. Real architecture bug: "Data Table - Buscar Memoria" sat inline in the
41-candidate chain (Mesclar Contexto -> Buscar Memoria -> Decidir
Necessidade de IA). A bulk "get all rows" Data Table node run with 41
input items produced 41 identical fetches and its OWN output (the empty
memory table on a first run) replaced the 41 candidates entirely --
"Decidir Necessidade de IA" received 0 items and the whole main branch
silently skipped. Moved the memory fetch to a parallel branch off
Config (fires once) and added a real Merge node ("Merge - Aguardar
Memoria") to guarantee both the candidates and the memory rows have
executed before "Decidir Necessidade de IA" runs -- same synchronization
pattern already used for context and for the shadow comparison.
Also fixed a cosmetic bug found in the real message output: buildTopMessage
produced a quadruple newline between the header and the first ticket block
([header, '', ...blocks].join('\n\n') double-counts the blank line already
inserted by the '\n\n' join). Added a regression test.
Verified with a real execution (id 13599, 24/08/2026 19:25 UTC) against the
live GLPI database: 41 real candidates, 30 sent to OpenAI (capped), 11
fallback-capped, memory table populated with 41 real rows including
genuine WAITING_VENDOR/WAITING_REQUESTER classifications with real
blocker_type/waiting_for fields extracted from real ticket task content,
and a real shadow_runs row comparing Legacy x V2 (4 tickets removed by V2:
2 for INSUFFICIENT_CONTEXT, 1 WAITING_REQUESTER, 1 WAITING_VENDOR; 4 added).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
a99a25cb1a |
feat(v2): complete n8n workflow template (36 nodes) covering full V2 pipeline + shadow branch
- 14 new Code node drivers implementing the pipeline stages: normalize
candidates, build context SQL, merge context + hash, decide AI need,
prepare AI prompt, validate AI response, AI-disabled fallback, reuse
cached classification, compute score, select top 10, prepare memory/
envio records, build message/digest, compare legacy x V2, debug panel.
- Consolidated the two context queries (followups/tasks) into a single
UNION ALL query tagged by tipo, and added a "Merge - Unir Shadow"
synchronization node before comparing legacy x V2: n8n does not
guarantee relative execution order between sibling branches that only
share an early common ancestor, so cross-branch data was only safe to
read by name once a real Merge node forced both to complete first.
- Reordered score calculation before the memory upsert (score is needed
in the persisted row) and turned both Data Table writes into
side-effect branches that fan out from the main path instead of
gating it, so the Data Table node's own output shape never needs to
carry the full pipeline state forward.
- Fixed a duplicate-identifier bug in the build script: score.js's
require('./cooldown') survived inlining and collided with
cooldown.js's own computeCooldown function in the same Code node
scope. build-workflow.js now strips inter-module requires when
inlining, and a new test syntax-checks every generated jsCode string
plus asserts no leftover require() calls.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|