Agent IA planificateur EVCS en MODE OMBRE (Advisor Shadow V1)
- Workflow n8n tdfInZns7cqo8fU2 actif (15 min): snapshot HA compact -> prompt FR -> LLM local Spark (modèle dynamique via /v1/models) avec fallback OpenRouter claude-haiku-4.5 (credential chiffré, clé hors git) -> validation stricte (enums, bornes, rejet total si invalide) -> helpers advisor_* + divergence + compteurs accord/divergence + logbook. - GARANTIE OMBRE structurelle: écritures limitées à advisor_*/logbook, vérifiée par tests/advisor/run.mjs (16 tests, bloquants au push). - Kill-switch input_boolean.evcs_advisor_enabled. - Extension R2 multi-workflows (lib-node-map WORKFLOWS[], build/extract/push par fichier, n8n-create.mjs, gen-advisor-workflow.mjs). - Helpers HA via package NFS + carte dashboard EVCS Master (backup fait). - 1re reco réelle: auto_solar_plus, cible 35%, accord avec le réel. - Fix sécurité n8n-push: plus de fallback d'id implicite vers le workflow EVCS.
This commit is contained in:
214
tools/gen-advisor-workflow.mjs
Normal file
214
tools/gen-advisor-workflow.mjs
Normal file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env node
|
||||
// Génère workflows/evcs-advisor-shadow-v1.json (squelette : nodes, connexions,
|
||||
// credentials, positions). Le CODE des nodes est ensuite injecté depuis
|
||||
// src/advisor/ par tools/n8n-build.mjs (source de vérité R2).
|
||||
// Rejouable : écrase le fichier local (pas le serveur).
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
const HA_CRED = { id: 'uv3sknYYigxBkaVN', name: 'Bearer Auth account 2' };
|
||||
const OPENROUTER_CRED = { id: 'eOM9xofDHT0KMoeY', name: 'OpenRouter Advisor' };
|
||||
const SPARK_URL = 'http://10.10.10.8:8000';
|
||||
const CLOUD_MODEL = 'anthropic/claude-haiku-4.5';
|
||||
const HA_URL = 'http://10.10.10.17:8123';
|
||||
|
||||
const nodes = [
|
||||
{
|
||||
name: 'Schedule Trigger',
|
||||
type: 'n8n-nodes-base.scheduleTrigger',
|
||||
typeVersion: 1.2,
|
||||
position: [0, 0],
|
||||
parameters: { rule: { interval: [{ field: 'minutes', minutesInterval: 15 }] } },
|
||||
},
|
||||
{
|
||||
name: 'HA Snapshot Advisor',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 4.2,
|
||||
position: [200, 0],
|
||||
parameters: {
|
||||
method: 'POST',
|
||||
url: `${HA_URL}/api/template`,
|
||||
authentication: 'genericCredentialType',
|
||||
genericAuthType: 'httpBearerAuth',
|
||||
sendHeaders: true,
|
||||
headerParameters: {
|
||||
parameters: [
|
||||
{ name: 'Content-Type', value: 'application/json' },
|
||||
{ name: 'Accept', value: 'text/plain' },
|
||||
],
|
||||
},
|
||||
sendBody: true,
|
||||
bodyParameters: { parameters: [{ name: 'template', value: '(injecté depuis src/advisor)' }] },
|
||||
options: { timeout: 15000 },
|
||||
},
|
||||
credentials: { httpBearerAuth: HA_CRED },
|
||||
},
|
||||
{
|
||||
name: 'BUILD_PROMPT',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 2,
|
||||
position: [400, 0],
|
||||
parameters: { jsCode: '// injecté depuis src/advisor/BUILD_PROMPT.js' },
|
||||
},
|
||||
{
|
||||
name: 'IF_ENABLED',
|
||||
type: 'n8n-nodes-base.if',
|
||||
typeVersion: 2.2,
|
||||
position: [600, 0],
|
||||
parameters: {
|
||||
conditions: {
|
||||
options: { caseSensitive: true, leftValue: '', typeValidation: 'loose', version: 2 },
|
||||
combinator: 'and',
|
||||
conditions: [
|
||||
{
|
||||
leftValue: '={{ $json.enabled }}',
|
||||
rightValue: '',
|
||||
operator: { type: 'boolean', operation: 'true', singleValue: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'LLM_LOCAL_MODELS',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 4.2,
|
||||
position: [800, -100],
|
||||
parameters: {
|
||||
method: 'GET',
|
||||
url: `${SPARK_URL}/v1/models`,
|
||||
options: { timeout: 5000 },
|
||||
},
|
||||
onError: 'continueRegularOutput',
|
||||
alwaysOutputData: true,
|
||||
},
|
||||
{
|
||||
name: 'LLM_LOCAL',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 4.2,
|
||||
position: [1000, -100],
|
||||
parameters: {
|
||||
method: 'POST',
|
||||
url: `${SPARK_URL}/v1/chat/completions`,
|
||||
sendHeaders: true,
|
||||
headerParameters: { parameters: [{ name: 'Content-Type', value: 'application/json' }] },
|
||||
sendBody: true,
|
||||
specifyBody: 'json',
|
||||
jsonBody:
|
||||
"={{ JSON.stringify({ model: $('LLM_LOCAL_MODELS').first().json.data[0].id, messages: $('BUILD_PROMPT').first().json.messages, temperature: 0.1, max_tokens: 6000 }) }}",
|
||||
options: { timeout: 120000 },
|
||||
},
|
||||
onError: 'continueRegularOutput',
|
||||
alwaysOutputData: true,
|
||||
},
|
||||
{
|
||||
name: 'IF_LOCAL_OK',
|
||||
type: 'n8n-nodes-base.if',
|
||||
typeVersion: 2.2,
|
||||
position: [1200, -100],
|
||||
parameters: {
|
||||
conditions: {
|
||||
options: { caseSensitive: true, leftValue: '', typeValidation: 'loose', version: 2 },
|
||||
combinator: 'and',
|
||||
conditions: [
|
||||
{
|
||||
leftValue: "={{ $json.choices !== undefined && $json.choices.length > 0 }}",
|
||||
rightValue: '',
|
||||
operator: { type: 'boolean', operation: 'true', singleValue: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'LLM_CLOUD',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 4.2,
|
||||
position: [1400, 40],
|
||||
parameters: {
|
||||
method: 'POST',
|
||||
url: 'https://openrouter.ai/api/v1/chat/completions',
|
||||
authentication: 'genericCredentialType',
|
||||
genericAuthType: 'httpHeaderAuth',
|
||||
sendHeaders: true,
|
||||
headerParameters: { parameters: [{ name: 'Content-Type', value: 'application/json' }] },
|
||||
sendBody: true,
|
||||
specifyBody: 'json',
|
||||
jsonBody:
|
||||
`={{ JSON.stringify({ model: '${CLOUD_MODEL}', messages: $('BUILD_PROMPT').first().json.messages, temperature: 0.1, max_tokens: 1000 }) }}`,
|
||||
options: { timeout: 60000 },
|
||||
},
|
||||
credentials: { httpHeaderAuth: OPENROUTER_CRED },
|
||||
onError: 'continueRegularOutput',
|
||||
alwaysOutputData: true,
|
||||
},
|
||||
{
|
||||
name: 'VALIDATE_PARSE',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 2,
|
||||
position: [1600, -30],
|
||||
parameters: { jsCode: '// injecté depuis src/advisor/VALIDATE_PARSE.js' },
|
||||
},
|
||||
{
|
||||
name: 'BUILD_HA_CALLS',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 2,
|
||||
position: [1800, -30],
|
||||
parameters: { jsCode: '// injecté depuis src/advisor/BUILD_HA_CALLS.js' },
|
||||
},
|
||||
{
|
||||
name: 'Execute HA Service Advisor',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 4.2,
|
||||
position: [2000, -30],
|
||||
parameters: {
|
||||
method: 'POST',
|
||||
url: "={{ 'http://10.10.10.17:8123/api/services/' + $json.domain + '/' + $json.service }}",
|
||||
authentication: 'genericCredentialType',
|
||||
genericAuthType: 'httpBearerAuth',
|
||||
sendHeaders: true,
|
||||
headerParameters: {
|
||||
parameters: [
|
||||
{ name: 'Content-Type', value: 'application/json' },
|
||||
{ name: 'accept', value: 'application/json' },
|
||||
],
|
||||
},
|
||||
sendBody: true,
|
||||
specifyBody: 'json',
|
||||
jsonBody: '={{ $json.data }}',
|
||||
options: { timeout: 15000 },
|
||||
},
|
||||
credentials: { httpBearerAuth: HA_CRED },
|
||||
onError: 'continueRegularOutput',
|
||||
},
|
||||
];
|
||||
|
||||
const connections = {
|
||||
'Schedule Trigger': { main: [[{ node: 'HA Snapshot Advisor', type: 'main', index: 0 }]] },
|
||||
'HA Snapshot Advisor': { main: [[{ node: 'BUILD_PROMPT', type: 'main', index: 0 }]] },
|
||||
BUILD_PROMPT: { main: [[{ node: 'IF_ENABLED', type: 'main', index: 0 }]] },
|
||||
IF_ENABLED: { main: [[{ node: 'LLM_LOCAL_MODELS', type: 'main', index: 0 }], []] },
|
||||
LLM_LOCAL_MODELS: { main: [[{ node: 'LLM_LOCAL', type: 'main', index: 0 }]] },
|
||||
LLM_LOCAL: { main: [[{ node: 'IF_LOCAL_OK', type: 'main', index: 0 }]] },
|
||||
IF_LOCAL_OK: {
|
||||
main: [
|
||||
[{ node: 'VALIDATE_PARSE', type: 'main', index: 0 }],
|
||||
[{ node: 'LLM_CLOUD', type: 'main', index: 0 }],
|
||||
],
|
||||
},
|
||||
LLM_CLOUD: { main: [[{ node: 'VALIDATE_PARSE', type: 'main', index: 0 }]] },
|
||||
VALIDATE_PARSE: { main: [[{ node: 'BUILD_HA_CALLS', type: 'main', index: 0 }]] },
|
||||
BUILD_HA_CALLS: { main: [[{ node: 'Execute HA Service Advisor', type: 'main', index: 0 }]] },
|
||||
};
|
||||
|
||||
const workflow = {
|
||||
name: 'EVCS Advisor Shadow V1',
|
||||
nodes,
|
||||
connections,
|
||||
settings: { executionOrder: 'v1', callerPolicy: 'workflowsFromSameOwner' },
|
||||
staticData: null,
|
||||
};
|
||||
|
||||
fs.writeFileSync('workflows/evcs-advisor-shadow-v1.json', JSON.stringify(workflow, null, 2) + '\n');
|
||||
console.log(`Squelette écrit: workflows/evcs-advisor-shadow-v1.json (${nodes.length} nodes)`);
|
||||
console.log('→ lancer: node tools/n8n-build.mjs workflows/evcs-advisor-shadow-v1.json');
|
||||
Reference in New Issue
Block a user