Pyragogy AI Village - Maître d'orchestration (Architecture profonde V2)

Avancé

Ceci est unEngineering, AIworkflow d'automatisation du domainecontenant 35 nœuds.Utilise principalement des nœuds comme If, Wait, Merge, Slack, Start, combinant la technologie d'intelligence artificielle pour une automatisation intelligente. Utiliser l'orchestration multi-agents GPT-4o et une révision humaine pour générer un manuel collaboratif

Prérequis
  • Token Bot Slack ou URL Webhook
  • Personal Access Token GitHub
  • Clé API OpenAI
  • Point de terminaison HTTP Webhook (généré automatiquement par n8n)
  • Informations de connexion à la base de données PostgreSQL
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2)",
  "tags": [
    {
      "id": "pyragogy",
      "name": "Pyragogy"
    },
    {
      "id": "multi-agent",
      "name": "Multi-Agent"
    },
    {
      "id": "orchestration",
      "name": "Orchestration"
    },
    {
      "id": "human-in-loop",
      "name": "Human-in-Loop"
    }
  ],
  "nodes": [
    {
      "name": "Start",
      "type": "n8n-nodes-base.start",
      "position": [
        50,
        300
      ],
      "parameters": {},
      "typeVersion": 1,
      "id": "Start-0"
    },
    {
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "position": [
        250,
        300
      ],
      "webhookId": "pyragogy-master-trigger",
      "parameters": {
        "path": "pyragogy/process",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1,
      "id": "Webhook-Trigger-1"
    },
    {
      "name": "Vérifier la Connexion à la Base de Données",
      "type": "n8n-nodes-base.postgres",
      "position": [
        450,
        300
      ],
      "parameters": {
        "query": "SELECT 1; -- Verifica connessione DB",
        "options": {},
        "operation": "executeQuery"
      },
      "credentials": {
        "postgres": {
          "id": "pyragogy-postgres",
          "name": "Postgres Pyragogy DB"
        }
      },
      "typeVersion": 1,
      "id": "V-rifier-la-Connexion-la-Base-de-Donn-es-2"
    },
    {
      "name": "Méta-Orchestrateur",
      "type": "n8n-nodes-base.openAi",
      "position": [
        650,
        300
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {
          "response_format": {
            "type": "json_object"
          }
        },
        "messages": [
          {
            "role": "system",
            "content": "You are the Meta-orchestrator of the Pyragogy AI Village. Your task is to analyze the input and determine the optimal agent sequence for processing. Consider the input type, complexity, and goals. Available agents: Summarizer, Synthesizer, Peer Reviewer, Sensemaking Agent, Prompt Engineer, Onboarding/Explainer, Archivist. Return a JSON array of agent names in the order they should run, e.g., [\"Summarizer\", \"Synthesizer\", \"Peer Reviewer\", \"Archivist\"]. Include \"Archivist\" last if persistence is needed."
          },
          {
            "role": "user",
            "content": "Input Data:\n{{ JSON.stringify($json.body) }}"
          }
        ],
        "resource": "chat",
        "authentication": "apiKey"
      },
      "credentials": {
        "openAiApi": {
          "id": "pyragogy-openai",
          "name": "OpenAI Pyragogy"
        }
      },
      "typeVersion": 1,
      "id": "M-ta-Orchestrateur-3"
    },
    {
      "name": "Analyser le Plan d'Orchestration",
      "type": "n8n-nodes-base.function",
      "position": [
        850,
        300
      ],
      "parameters": {
        "functionCode": "// Analizza il piano di orchestrazione e imposta per il looping\nlet rawPlan = $json.choices[0].message.content;\nlet plan;\n\ntry {\n  plan = JSON.parse(rawPlan);\n} catch (e) {\n  // Se il parsing fallisce, assumi che sia una stringa di array grezza\n  plan = rawPlan;\n}\n\n// Estrai in modo sicuro la sequenza degli agenti:\n// Se 'plan' è un oggetto e ha una chiave 'agents', usala.\n// Altrimenti, se 'plan' è un array, usalo direttamente.\n// Altrimenti, predefinisci un array vuoto.\nconst agentSequence = Array.isArray(plan) ? plan : (plan && plan.agents && Array.isArray(plan.agents) ? plan.agents : []);\n\n// Memorizza la sequenza e l'indice corrente per il loop\n$workflow.agentSequence = agentSequence;\n$workflow.currentAgentIndex = 0;\n$workflow.redraftLoopCount = 0; // Inizializza il contatore dei cicli di rielaborazione\n\n// Passa i dati di input al primo agente, assicurandosi che $items[0].json.body esista\nconst initialInput = $items[0] && $items[0].json && $items[0].json.body ? $items[0].json.body : {};\n\nreturn [{ json: { ...initialInput, agentToRun: agentSequence[0] || null } }];"
      },
      "typeVersion": 1,
      "id": "Analyser-le-Plan-d-Orchestration-4"
    },
    {
      "name": "Plus d'Agents à Exécuter ?",
      "type": "n8n-nodes-base.if",
      "position": [
        1050,
        300
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $workflow.currentAgentIndex < $workflow.agentSequence.length }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1,
      "id": "Plus-d-Agents-Ex-cuter--5"
    },
    {
      "name": "Préparer l'Entrée de l'Agent",
      "type": "n8n-nodes-base.function",
      "position": [
        1250,
        200
      ],
      "parameters": {
        "functionCode": "// Ottieni il nome dell'agente corrente\nconst agentName = $workflow.agentSequence[$workflow.currentAgentIndex];\n\n// Prepara i dati per l'esecuzione dell'agente\n// Passa l'output del passo precedente (o l'input iniziale).\n// Se stiamo rielaborando, l'input dell'agente dovrebbe includere il feedback di rielaborazione.\nconst previousOutput = $json.output || $json.body.input; // Assumendo che l'output dell'agente sia memorizzato nella chiave 'output'\nconst agentInput = $json.redraftInput || previousOutput; // Usa redraftInput se presente, altrimenti previousOutput\n\nreturn [{ json: { ...$json, agentToRun: agentName, agentInput: agentInput } }];"
      },
      "typeVersion": 1,
      "id": "Pr-parer-l-Entr-e-de-l-Agent-6"
    },
    {
      "name": "Router les Agents avec Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        1450,
        200
      ],
      "parameters": {
        "mode": "json",
        "value": "={{ $json.agentToRun }}",
        "conditions": [
          {
            "type": "string",
            "value": "Summarizer"
          },
          {
            "type": "string",
            "value": "Synthesizer"
          },
          {
            "type": "string",
            "value": "Peer Reviewer"
          },
          {
            "type": "string",
            "value": "Sensemaking Agent"
          },
          {
            "type": "string",
            "value": "Prompt Engineer"
          },
          {
            "type": "string",
            "value": "Onboarding/Explainer"
          },
          {
            "type": "string",
            "value": "Archivist"
          }
        ]
      },
      "typeVersion": 1,
      "id": "Router-les-Agents-avec-Switch-7"
    },
    {
      "name": "Agent de Synthèse",
      "type": "n8n-nodes-base.openAi",
      "position": [
        1650,
        0
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "messages": [
          {
            "role": "system",
            "content": "You are the Summarizer Agent. Summarize the provided text into 3 key points."
          },
          {
            "role": "user",
            "content": "Text to summarize:\n{{ $json.agentInput }}"
          }
        ],
        "resource": "chat",
        "authentication": "apiKey"
      },
      "credentials": {
        "openAiApi": {
          "id": "pyragogy-openai",
          "name": "OpenAI Pyragogy"
        }
      },
      "typeVersion": 1,
      "id": "Agent-de-Synth-se-8"
    },
    {
      "name": "Agent de Synthétiseur",
      "type": "n8n-nodes-base.openAi",
      "position": [
        1650,
        100
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "messages": [
          {
            "role": "system",
            "content": "You are the Synthesizer Agent. Synthesize a creative new text from the given key points or input. If provided with 'redraft_feedback', incorporate it to refine the output."
          },
          {
            "role": "user",
            "content": "Input for synthesis:\n{{ $json.agentInput }}\n\n{{ $json.redraftFeedback ? 'Feedback per la rielaborazione: ' + $json.redraftFeedback : '' }}"
          }
        ],
        "resource": "chat",
        "authentication": "apiKey"
      },
      "credentials": {
        "openAiApi": {
          "id": "pyragogy-openai",
          "name": "OpenAI Pyragogy"
        }
      },
      "typeVersion": 1,
      "id": "Agent-de-Synth-tiseur-9"
    },
    {
      "name": "Agent de Relecture par les Pairs",
      "type": "n8n-nodes-base.openAi",
      "position": [
        1650,
        200
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {
          "response_format": {
            "type": "json_object"
          }
        },
        "messages": [
          {
            "role": "system",
            "content": "You are the Peer Reviewer Agent. Review the provided text, highlight strengths, weaknesses, and provide actionable suggestions for improvement. In your JSON output, include a 'major_issue' boolean flag (true if significant redrafting is needed, false otherwise)."
          },
          {
            "role": "user",
            "content": "Text to review:\n{{ $json.agentInput }}"
          }
        ],
        "resource": "chat",
        "authentication": "apiKey"
      },
      "credentials": {
        "openAiApi": {
          "id": "pyragogy-openai",
          "name": "OpenAI Pyragogy"
        }
      },
      "typeVersion": 1,
      "id": "Agent-de-Relecture-par-les-Pairs-10"
    },
    {
      "name": "Agent de Sensemaking",
      "type": "n8n-nodes-base.openAi",
      "position": [
        1650,
        300
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {
          "response_format": {
            "type": "json_object"
          }
        },
        "messages": [
          {
            "role": "system",
            "content": "You are the Sensemaking Agent. Analyze the input, connect it with existing knowledge (context provided), identify patterns, gaps, and suggest new directions. In your JSON output, include a 'major_issue' boolean flag (true if significant redrafting is needed, false otherwise)."
          },
          {
            "role": "user",
            "content": "Input to analyze:\n{{ $json.agentInput }}\n\nContext from DB (if available):\n{{ $json.dbContext }}"
          }
        ],
        "resource": "chat",
        "authentication": "apiKey"
      },
      "credentials": {
        "openAiApi": {
          "id": "pyragogy-openai",
          "name": "OpenAI Pyragogy"
        }
      },
      "typeVersion": 1,
      "id": "Agent-de-Sensemaking-11"
    },
    {
      "name": "Agent d'Ingénierie de Prompt",
      "type": "n8n-nodes-base.openAi",
      "position": [
        1650,
        400
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {
          "response_format": {
            "type": "json_object"
          }
        },
        "messages": [
          {
            "role": "system",
            "content": "You are the Prompt Engineer Agent. Analyze the current task context and the next agent in the sequence. Refine or generate an optimal prompt for the next agent. In your JSON output, include a 'major_issue' boolean flag (true if significant redrafting is needed, false otherwise)."
          },
          {
            "role": "user",
            "content": "Current context:\n{{ JSON.stringify($json) }}\nNext agent: {{ $workflow.agentSequence[$workflow.currentAgentIndex + 1] || 'None' }}"
          }
        ],
        "resource": "chat",
        "authentication": "apiKey"
      },
      "credentials": {
        "openAiApi": {
          "id": "pyragogy-openai",
          "name": "OpenAI Pyragogy"
        }
      },
      "typeVersion": 1,
      "id": "Agent-d-Ing-nierie-de-Prompt-12"
    },
    {
      "name": "Agent d'Onboarding/Explication",
      "type": "n8n-nodes-base.openAi",
      "position": [
        1650,
        500
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {},
        "messages": [
          {
            "role": "system",
            "content": "You are the Onboarding/Explainer Agent. Explain the current process, the result achieved so far, or provide guidance based on the input."
          },
          {
            "role": "user",
            "content": "Explain the following:\n{{ $json.agentInput }}"
          }
        ],
        "resource": "chat",
        "authentication": "apiKey"
      },
      "credentials": {
        "openAiApi": {
          "id": "pyragogy-openai",
          "name": "OpenAI Pyragogy"
        }
      },
      "typeVersion": 1,
      "id": "Agent-d-Onboarding-Explication-13"
    },
    {
      "name": "Ajouter les Métadonnées du Manuel",
      "type": "n8n-nodes-base.function",
      "position": [
        1650,
        600
      ],
      "parameters": {
        "functionCode": "// Prepara i metadati per il contenuto dell'Handbook.\n// Assicurati che l'input originale contenga 'title' e 'tags' o imposta dei valori predefiniti.\nconst title = $json.body.title || 'Untitled Handbook Entry';\nconst tags = $json.body.tags || [];\nconst phase = $json.body.phase || 'draft'; // Fase iniziale, può essere 'final' dopo l'approvazione\nconst rhythm = $json.body.rhythm || 'on-demand'; // Ritmo cognitivo\n\nreturn [{ json: { ...$json, handbookTitle: title, handbookTags: tags, handbookPhase: phase, handbookRhythm: rhythm } }];"
      },
      "typeVersion": 1,
      "id": "Ajouter-les-M-tadonn-es-du-Manuel-14"
    },
    {
      "name": "Générer le Contenu pour Révision",
      "type": "n8n-nodes-base.function",
      "position": [
        1850,
        600
      ],
      "parameters": {
        "functionCode": "// Prepara il contenuto proposto dall'Archivista per la revisione umana, inclusa la formattazione YAML.\n// Assicurati che l'input dell'agente (il contenuto generato) sia disponibile.\nconst proposedContent = $json.agentInput;\nconst title = $json.handbookTitle;\nconst tags = $json.handbookTags;\nconst phase = $json.handbookPhase;\nconst rhythm = $json.handbookRhythm;\n\n// Costruisci il front-matter YAML\nconst yamlFrontMatter = `---\ntitle: \"${title.replace(/\"/g, '\\\"')}\"\ntags: [${tags.map(t => `\"${t.replace(/\"/g, '\\\"')}\"`).join(', ')}]\nphase: \"${phase}\"\nrhythm: \"${rhythm}\"\n---\n\n`;\n\nconst finalMarkdownContent = yamlFrontMatter + proposedContent;\n\nreturn [{ json: { ...$json, proposedContent: proposedContent, reviewTitle: title, reviewTags: tags, finalMarkdownContent: finalMarkdownContent } }];"
      },
      "typeVersion": 1,
      "id": "G-n-rer-le-Contenu-pour-R-vision-15"
    },
    {
      "name": "Générer l'ID de Révision",
      "type": "n8n-nodes-base.function",
      "position": [
        2050,
        600
      ],
      "parameters": {
        "functionCode": "// Genera un ID univoco per questa richiesta di revisione.\n// Questo ID verrà usato per correlare la risposta del revisore con questa istanza del workflow.\nconst reviewId = crypto.randomUUID();\n\nreturn [{ json: { ...$json, reviewId: reviewId } }];"
      },
      "typeVersion": 1,
      "id": "G-n-rer-l-ID-de-R-vision-16"
    },
    {
      "name": "Envoyer l'Email de Demande de Révision",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2250,
        600
      ],
      "parameters": {
        "html": "<h3>Revisione Contenuto Pyragogy Handbook: {{ $json.reviewTitle }}</h3>\n<p>Ciao revisore,</p>\n<p>È stato proposto un nuovo contenuto per l'Handbook:</p>\n<hr>\n<pre>{{ $json.proposedContent }}</pre>\n<hr>\n<p><strong>Titolo:</strong> {{ $json.reviewTitle }}</p>\n<p><strong>Tags:</strong> {{ $json.reviewTags.join(', ') }}</p>\n<p>Per favore, clicca su uno dei seguenti link per approvare o rifiutare:</p>\n<p><a href=\"your_n8n_url/webhook/pyragogy/review-feedback?reviewId={{ $json.reviewId }}&status=approved\">Approva</a></p>\n<p><a href=\"your_n8n_url/webhook/pyragogy/review-feedback?reviewId={{ $json.reviewId }}&status=rejected\">Rifiuta</a></p>\n<p>Grazie!</p>",
        "text": "Ciao revisore,\n\nÈ stato proposto un nuovo contenuto per l'Handbook:\n\n---\n{{ $json.proposedContent }}\n---\n\nTitolo: {{ $json.reviewTitle }}\nTags: {{ $json.reviewTags.join(', ') }}\n\nPer favore, clicca su uno dei seguenti link per approvare o rifiutare:\n\nApprova: your_n8n_url/webhook/pyragogy/review-feedback?reviewId={{ $json.reviewId }}&status=approved\nRifiuta: your_n8n_url/webhook/pyragogy/review-feedback?reviewId={{ $json.reviewId }}&status=rejected\n\nGrazie!",
        "options": {},
        "subject": "Revisione Contenuto Pyragogy Handbook: {{ $json.reviewTitle }}",
        "toEmail": "human-reviewer@example.com",
        "fromEmail": "your-email@example.com"
      },
      "credentials": {
        "emailSend": {
          "id": "your-email-credential-id",
          "name": "Your Email Credential Name"
        }
      },
      "typeVersion": 1,
      "id": "Envoyer-l-Email-de-Demande-de-R-vision-17"
    },
    {
      "name": "Attendre l'Approbation Humaine",
      "type": "n8n-nodes-base.wait",
      "position": [
        2450,
        600
      ],
      "parameters": {
        "mode": "webhook",
        "timeout": "1h",
        "matchField": "reviewId",
        "matchValue": "={{ $json.reviewId }}",
        "webhookPath": "pyragogy/review-feedback"
      },
      "typeVersion": 1,
      "id": "Attendre-l-Approbation-Humaine-18"
    },
    {
      "name": "Décision Humaine - Split",
      "type": "n8n-nodes-base.if",
      "position": [
        2650,
        600
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.query.status === 'approved' }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1,
      "id": "D-cision-Humaine---Split-19"
    },
    {
      "name": "Sauvegarder dans handbook_entries",
      "type": "n8n-nodes-base.postgres",
      "position": [
        2850,
        500
      ],
      "parameters": {
        "table": "handbook_entries",
        "values": "={{ $json.reviewTitle || 'Untitled' }}, {{ $json.finalMarkdownContent }}, {{ $json.version || 1 }}, {{ $json.author || 'AI Village' }}, ARRAY[{{ ($json.reviewTags || []).map(t => `'${t}'`).join(',') }}], {{ $json.handbookPhase || 'final' }}, {{ $json.handbookRhythm || 'on-demand' }}",
        "columns": "title, content, version, created_by, tags, phase, rhythm",
        "options": {
          "returning": "id"
        },
        "operation": "insert"
      },
      "credentials": {
        "postgres": {
          "id": "pyragogy-postgres",
          "name": "Postgres Pyragogy DB"
        }
      },
      "typeVersion": 1,
      "id": "Sauvegarder-dans-handbook_entries-20"
    },
    {
      "name": "Préparer les Données de Contribution Approuvées",
      "type": "n8n-nodes-base.function",
      "position": [
        3050,
        500
      ],
      "parameters": {
        "functionCode": "// Registra il contributo dell'agente dopo l'approvazione umana\nconst entryId = $json.id; // ID dall'inserimento in handbook_entries\nconst agentName = 'Archivist';\nconst contributionType = 'Archiving (Approved)';\nconst details = { input: $json.proposedContent, metadata: { title: $json.reviewTitle, version: $json.version, tags: $json.reviewTags, phase: $json.handbookPhase, rhythm: $json.handbookRhythm }, reviewStatus: 'approved' };\n\n$items[0].json.contribution = { entryId, agentName, contributionType, details };\nreturn $items;"
      },
      "typeVersion": 1,
      "id": "Pr-parer-les-Donn-es-de-Contribution-Approuv-es-21"
    },
    {
      "name": "Sauvegarder la Contribution d'Agent (Approuvée)",
      "type": "n8n-nodes-base.postgres",
      "position": [
        3250,
        500
      ],
      "parameters": {
        "table": "agent_contributions",
        "values": "={{ $json.contribution.entryId }}, {{ $json.contribution.agentName }}, {{ $json.contribution.contributionType }}, {{ JSON.stringify($json.contribution.details) }}",
        "columns": "entry_id, agent_name, contribution_type, details",
        "options": {},
        "operation": "insert"
      },
      "credentials": {
        "postgres": {
          "id": "pyragogy-postgres",
          "name": "Postgres Pyragogy DB"
        }
      },
      "typeVersion": 1,
      "id": "Sauvegarder-la-Contribution-d-Agent-Approuv-e--22"
    },
    {
      "name": "Générer le Chemin de Fichier GitHub",
      "type": "n8n-nodes-base.function",
      "position": [
        3450,
        500
      ],
      "parameters": {
        "functionCode": "// Genera il percorso del file GitHub con slug e timestamp per il versioning.\nconst chapterSlug = ($json.reviewTitle || 'untitled').replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();\nconst timestamp = new Date().toISOString().replace(/[:.-]/g, ''); // Rimuove caratteri non validi per i nomi di file\nconst filePathWithVersion = `content/${chapterSlug}_v${timestamp}.md`;\n\nreturn [{ json: { ...$json, githubFilePath: filePathWithVersion } }];"
      },
      "typeVersion": 1,
      "id": "G-n-rer-le-Chemin-de-Fichier-GitHub-23"
    },
    {
      "name": "GitHub Activé ?",
      "type": "n8n-nodes-base.if",
      "position": [
        3650,
        500
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $env.GITHUB_ACCESS_TOKEN }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1,
      "id": "GitHub-Activ--24"
    },
    {
      "name": "Commiter vers GitHub (Approuvé)",
      "type": "n8n-nodes-base.github",
      "position": [
        3850,
        500
      ],
      "parameters": {
        "owner": "={{ $env.GITHUB_REPOSITORY_OWNER }}",
        "options": {},
        "filePath": "={{ $json.githubFilePath }}",
        "resource": "file",
        "operation": "createUpdate",
        "repository": "={{ $env.GITHUB_REPOSITORY_NAME }}",
        "fileContent": "={{ $json.finalMarkdownContent }}",
        "commitMessage": "={{ `[BOT] Add/Update Handbook: ${$json.reviewTitle} (Approved by Human)` }}",
        "authentication": "accessToken"
      },
      "credentials": {
        "githubApi": {
          "id": "pyragogy-github",
          "name": "GitHub Pyragogy"
        }
      },
      "typeVersion": 1,
      "id": "Commiter-vers-GitHub-Approuv--25"
    },
    {
      "name": "Journaliser le Rejet Humain",
      "type": "n8n-nodes-base.function",
      "position": [
        2850,
        700
      ],
      "parameters": {
        "functionCode": "// Registra il rifiuto umano del contenuto\nconst agentName = 'Archivist';\nconst reviewId = $json.reviewId;\nconst reviewStatus = 'rejected';\nconst reviewComments = $json.query.comments || 'Nessun commento fornito.';\nconst proposedContent = $json.proposedContent;\nconst title = $json.reviewTitle;\n\nconsole.log(`Contenuto proposto dall'Archivista (ID: ${reviewId}, Titolo: ${title}) rifiutato dall'umano. Commenti: ${reviewComments}`);\n\n// Prepara l'output per indicare il rifiuto e consentire al flusso di continuare.\nreturn [{ json: { ...$json, reviewStatus: reviewStatus, reviewComments: reviewComments, output: { message: `Archivista: Contenuto rifiutato dall'umano.`, status: 'rejected', comments: reviewComments } } }];"
      },
      "typeVersion": 1,
      "id": "Journaliser-le-Rejet-Humain-26"
    },
    {
      "name": "Fusionner les Chemins d'Archiviste",
      "type": "n8n-nodes-base.merge",
      "position": [
        4050,
        600
      ],
      "parameters": {
        "mode": "mergeByPropertyName",
        "propertyName": "reviewId"
      },
      "typeVersion": 1,
      "id": "Fusionner-les-Chemins-d-Archiviste-27"
    },
    {
      "name": "Évaluer le Consensus du Conseil",
      "type": "n8n-nodes-base.function",
      "position": [
        1850,
        300
      ],
      "parameters": {
        "functionCode": "// Valuta i flag 'major_issue' dagli agenti di revisione per determinare se è necessaria una rielaborazione.\nlet majorIssueCount = 0;\nlet redraftFeedback = '';\n\n// Assumi che gli output degli agenti di revisione siano accessibili tramite $node\n// (Ad esempio, se Peer Reviewer -> Sensemaking -> Prompt Engineer sono sequenziali prima di questo nodo)\n\nif ($node[\"Peer Reviewer Agent\"] && $node[\"Peer Reviewer Agent\"].json && $node[\"Peer Reviewer Agent\"].json.choices && $node[\"Peer Reviewer Agent\"].json.choices[0] && $node[\"Peer Reviewer Agent\"].json.choices[0].message && $node[\"Peer Reviewer Agent\"].json.choices[0].message.content) {\n    const peerReviewOutput = JSON.parse($node[\"Peer Reviewer Agent\"].json.choices[0].message.content);\n    if (peerReviewOutput.major_issue) majorIssueCount++;\n    redraftFeedback += `Peer Reviewer: ${peerReviewOutput.suggestions || ''}\\n`;\n}\n\nif ($node[\"Sensemaking Agent\"] && $node[\"Sensemaking Agent\"].json && $node[\"Sensemaking Agent\"].json.choices && $node[\"Sensemaking Agent\"].json.choices[0] && $node[\"Sensemaking Agent\"].json.choices[0].message && $node[\"Sensemaking Agent\"].json.choices[0].message.content) {\n    const sensemakingOutput = JSON.parse($node[\"Sensemaking Agent\"].json.choices[0].message.content);\n    if (sensemakingOutput.major_issue) majorIssueCount++;\n    redraftFeedback += `Sensemaking Agent: ${sensemakingOutput.suggestions || ''}\\n`;\n}\n\nif ($node[\"Prompt Engineer Agent\"] && $node[\"Prompt Engineer Agent\"].json && $node[\"Prompt Engineer Agent\"].json.choices && $node[\"Prompt Engineer Agent\"].json.choices[0] && $node[\"Prompt Engineer Agent\"].json.choices[0].message && $node[\"Prompt Engineer Agent\"].json.choices[0].message.content) {\n    const promptEngineerOutput = JSON.parse($node[\"Prompt Engineer Agent\"].json.choices[0].message.content);\n    if (promptEngineerOutput.major_issue) majorIssueCount++;\n    redraftFeedback += `Prompt Engineer: ${promptEngineerOutput.suggestions || ''}\\n`;\n}\n\nconst redraftNeeded = majorIssueCount >= 2; // Voto a maggioranza\n\nreturn [{ json: { ...$json, redraftNeeded: redraftNeeded, redraftFeedback: redraftFeedback } }];"
      },
      "typeVersion": 1,
      "id": "-valuer-le-Consensus-du-Conseil-28"
    },
    {
      "name": "Vérifier si une Réécriture est Nécessaire",
      "type": "n8n-nodes-base.if",
      "position": [
        2050,
        300
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.redraftNeeded && $workflow.redraftLoopCount < 2 }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1,
      "id": "V-rifier-si-une-R-criture-est-N-cessaire-29"
    },
    {
      "name": "Gérer la Réécriture",
      "type": "n8n-nodes-base.function",
      "position": [
        2250,
        200
      ],
      "parameters": {
        "functionCode": "// Gestisce la logica di rielaborazione: incrementa il contatore e reindirizza al Synthesizer.\n\n$workflow.redraftLoopCount += 1; // Incrementa il contatore del ciclo di rielaborazione\n\n// Trova l'indice del Synthesizer nella sequenza degli agenti\nconst synthesizerIndex = $workflow.agentSequence.indexOf(\"Synthesizer\");\n\n// Se il Synthesizer non è il prossimo agente, imposta l'indice corrente per farlo ripartire dal Synthesizer.\n// Questo garantisce che il Synthesizer venga eseguito successivamente per la rielaborazione.\nif ($workflow.currentAgentIndex !== synthesizerIndex) {\n    $workflow.currentAgentIndex = synthesizerIndex;\n} else {\n    // Se siamo già sul Synthesizer (es. dopo il primo passaggio del loop), assicurati che l'indice vada avanti normalmente nel prossimo ciclo.\n    // Questo è un caso limite, di solito il Prepare Agent Input lo gestirà.\n}\n\n// Passa il feedback di rielaborazione come input per il Synthesizer.\n// Il nodo 'Prepare Agent Input' utilizzerà questo campo per aggiornare 'agentInput'.\nreturn [{ json: { ...$json, redraftInput: $json.output + \"\\n\\nFeedback per la rielaborazione dal Peer Review Board:\\n\" + $json.redraftFeedback } }];"
      },
      "typeVersion": 1,
      "id": "G-rer-la-R-criture-30"
    },
    {
      "name": "Traiter la Sortie de l'Agent",
      "type": "n8n-nodes-base.function",
      "position": [
        2050,
        200
      ],
      "parameters": {
        "functionCode": "// Ottieni il nome dell'agente appena eseguito\nconst agentName = $json.agentToRun;\nlet agentOutput = '';\n\n// Costruisci dinamicamente il nome del nodo per il recupero dell'output\nlet actualNodeName;\n\n// Gestione speciale per l'Archivista dopo la revisione umana\nif (agentName === 'Archivist') {\n    if ($json.query && $json.query.status === 'approved') {\n        agentOutput = { message: 'Contenuto archiviato con successo dopo l'approvazione umana.', entryId: $json.id || 'N/A', handbookMetadata: { title: $json.reviewTitle, tags: $json.reviewTags, phase: $json.handbookPhase, rhythm: $json.handbookRhythm } };\n    } else if ($json.query && $json.query.status === 'rejected') {\n        agentOutput = { message: 'Archiviazione rifiutata dall'umano.', reviewComments: $json.query.comments || 'Nessun commento' };\n    } else if ($json.reviewStatus === 'rejected') {\n        // Caso in cui la revisione umana è stata rifiutata e si proviene dal ramo di rifiuto\n        agentOutput = { message: 'Archiviazione rifiutata dall'umano (tramite ramo di rifiuto).', reviewComments: $json.reviewComments || 'Nessun commento' };\n    } else {\n        agentOutput = { message: 'Processo Archivista: Attesa revisione umana o stato inatteso.', status: 'pending_review' };\n    }\n} else {\n    // Logica originale per gli altri agenti OpenAI\n    if (agentName === 'Onboarding/Explainer') {\n        actualNodeName = 'Onboarding/Explainer Agent';\n    } else {\n        actualNodeName = agentName + ' Agent';\n    }\n\n    // Tenta in modo sicuro di recuperare l'output dal nodo determinato dinamicamente\n    // Tenta anche di analizzare il JSON se l'output dell'agente è un oggetto JSON (come per gli agenti di revisione)\n    let rawContent = '';\n    if ($node[actualNodeName] && $node[actualNodeName].json && $node[actualNodeName].json.choices && $node[actualNodeName].json.choices[0] && $node[actualNodeName].json.choices[0].message && $node[actualNodeName].json.choices[0].message.content) {\n        rawContent = $node[actualNodeName].json.choices[0].message.content;\n    } else {\n        console.warn(`Impossibile trovare l'output chat OpenAI standard per il nodo: ${actualNodeName}. Ritorno al JSON grezzo.`);\n        agentOutput = $node[actualNodeName] ? $node[actualNodeName].json : `Nessun output specifico trovato per ${agentName}`;\n    }\n\n    // Tenta di analizzare il contenuto come JSON se l'agente è un agente di revisione\n    if (agentName === 'Peer Reviewer' || agentName === 'Sensemaking Agent' || agentName === 'Prompt Engineer') {\n        try {\n            agentOutput = JSON.parse(rawContent);\n        } catch (e) {\n            console.warn(`Impossibile analizzare l'output di ${agentName} come JSON. Trattato come stringa.`);\n            agentOutput = rawContent;\n        }\n    } else {\n        agentOutput = rawContent;\n    }\n}\n\n// Registra il contributo\nconst contribution = {\n    agent: agentName,\n    output: agentOutput,\n    timestamp: new Date().toISOString()\n};\n\n// Assicurati che l'array dei contributi esista e aggiungi il nuovo contributo\nconst existingContributions = Array.isArray($json.contributions) ? $json.contributions : [];\n\n// Incrementa l'indice dell'agente per il loop (solo se non siamo in rielaborazione e questo non è un agente di revisione che porta a rielaborazione)\n// Questo è gestito dalla logica di 'Handle Redraft' che forza l'indice per il riavvio.\nif (!($json.redraftNeeded && $workflow.redraftLoopCount < 2 && agentName !== 'Synthesizer')) { // Evita doppio incremento se riavvia il Synthesizer\n    $workflow.currentAgentIndex += 1;\n}\n\n// Restituisce l'elemento aggiornato, preservando l'input originale, aggiungendo l'output corrente e tutti i contributi\nreturn [{ json: { ...$items[0].json, output: agentOutput, contributions: [...existingContributions, contribution] } }];"
      },
      "typeVersion": 1,
      "id": "Traiter-la-Sortie-de-l-Agent-31"
    },
    {
      "name": "Slack Activé ?",
      "type": "n8n-nodes-base.if",
      "position": [
        1250,
        500
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $env.SLACK_WEBHOOK_URL }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1,
      "id": "Slack-Activ--32"
    },
    {
      "name": "Notifier Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1450,
        500
      ],
      "parameters": {
        "text": "Pyragogy AI Village Workflow Completato!\nInput: {{ $json.body.input }}\nOutput Finale: {{ JSON.stringify($json.output) }}\nAgenti Eseguiti: {{ $workflow.agentSequence.join(', ') }}",
        "options": {},
        "webhookUrl": "={{ $env.SLACK_WEBHOOK_URL }}"
      },
      "typeVersion": 1,
      "id": "Notifier-Slack-33"
    },
    {
      "name": "Réponse Finale",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1250,
        400
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ { finalOutput: $json.output, contributions: $json.contributions, agentSequence: $workflow.agentSequence } }}"
      },
      "typeVersion": 1,
      "id": "R-ponse-Finale-34"
    }
  ],
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Notifier-Slack-33": {
      "main": [
        [
          {
            "node": "R-ponse-Finale-34",
            "type": "main",
            "index": 0
          }
        ]
      ],
      "error": [
        [
          {
            "node": "R-ponse-Finale-34",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "G-rer-la-R-criture-30": {
      "main": [
        [
          {
            "node": "Plus-d-Agents-Ex-cuter--5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack-Activ--32": {
      "main": [
        [
          {
            "node": "Notifier-Slack-33",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "R-ponse-Finale-34",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub-Activ--24": {
      "main": [
        [
          {
            "node": "Commiter-vers-GitHub-Approuv--25",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fusionner-les-Chemins-d-Archiviste-27",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook-Trigger-1": {
      "main": [
        [
          {
            "node": "V-rifier-la-Connexion-la-Base-de-Donn-es-2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent-de-Synth-se-8": {
      "main": [
        [
          {
            "node": "Traiter-la-Sortie-de-l-Agent-31",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "M-ta-Orchestrateur-3": {
      "main": [
        [
          {
            "node": "Analyser-le-Plan-d-Orchestration-4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent-de-Sensemaking-11": {
      "main": [
        [
          {
            "node": "Agent-d-Ing-nierie-de-Prompt-12",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent-de-Synth-tiseur-9": {
      "main": [
        [
          {
            "node": "Traiter-la-Sortie-de-l-Agent-31",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "G-n-rer-l-ID-de-R-vision-16": {
      "main": [
        [
          {
            "node": "Envoyer-l-Email-de-Demande-de-R-vision-17",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "V-rifier-la-Connexion-la-Base-de-Donn-es-2": {
      "main": [
        [
          {
            "node": "M-ta-Orchestrateur-3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Journaliser-le-Rejet-Humain-26": {
      "main": [
        [
          {
            "node": "Fusionner-les-Chemins-d-Archiviste-27",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Plus-d-Agents-Ex-cuter--5": {
      "main": [
        [
          {
            "node": "Pr-parer-l-Entr-e-de-l-Agent-6",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack-Activ--32",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent-de-Relecture-par-les-Pairs-10": {
      "main": [
        [
          {
            "node": "Agent-de-Sensemaking-11",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pr-parer-l-Entr-e-de-l-Agent-6": {
      "main": [
        [
          {
            "node": "Router-les-Agents-avec-Switch-7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "V-rifier-si-une-R-criture-est-N-cessaire-29": {
      "main": [
        [
          {
            "node": "G-rer-la-R-criture-30",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Traiter-la-Sortie-de-l-Agent-31",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "D-cision-Humaine---Split-19": {
      "main": [
        [
          {
            "node": "Sauvegarder-dans-handbook_entries-20",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Journaliser-le-Rejet-Humain-26",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Traiter-la-Sortie-de-l-Agent-31": {
      "main": [
        [
          {
            "node": "Plus-d-Agents-Ex-cuter--5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ajouter-les-M-tadonn-es-du-Manuel-14": {
      "main": [
        [
          {
            "node": "G-n-rer-le-Contenu-pour-R-vision-15",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fusionner-les-Chemins-d-Archiviste-27": {
      "main": [
        [
          {
            "node": "Traiter-la-Sortie-de-l-Agent-31",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent-d-Ing-nierie-de-Prompt-12": {
      "main": [
        [
          {
            "node": "-valuer-le-Consensus-du-Conseil-28",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attendre-l-Approbation-Humaine-18": {
      "main": [
        [
          {
            "node": "D-cision-Humaine---Split-19",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "-valuer-le-Consensus-du-Conseil-28": {
      "main": [
        [
          {
            "node": "V-rifier-si-une-R-criture-est-N-cessaire-29",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyser-le-Plan-d-Orchestration-4": {
      "main": [
        [
          {
            "node": "Plus-d-Agents-Ex-cuter--5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Router-les-Agents-avec-Switch-7": {
      "main": [
        [
          {
            "node": "Agent-de-Synth-se-8",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Agent-de-Synth-tiseur-9",
            "type": "main",
            "index": 1
          }
        ],
        [
          {
            "node": "Agent-de-Relecture-par-les-Pairs-10",
            "type": "main",
            "index": 2
          }
        ],
        [
          {
            "node": "Agent-de-Sensemaking-11",
            "type": "main",
            "index": 3
          }
        ],
        [
          {
            "node": "Agent-d-Ing-nierie-de-Prompt-12",
            "type": "main",
            "index": 4
          }
        ],
        [
          {
            "node": "Agent-d-Onboarding-Explication-13",
            "type": "main",
            "index": 5
          }
        ],
        [
          {
            "node": "Ajouter-les-M-tadonn-es-du-Manuel-14",
            "type": "main",
            "index": 6
          }
        ]
      ]
    },
    "Sauvegarder-dans-handbook_entries-20": {
      "main": [
        [
          {
            "node": "Pr-parer-les-Donn-es-de-Contribution-Approuv-es-21",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "G-n-rer-le-Chemin-de-Fichier-GitHub-23": {
      "main": [
        [
          {
            "node": "GitHub-Activ--24",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Envoyer-l-Email-de-Demande-de-R-vision-17": {
      "main": [
        [
          {
            "node": "Attendre-l-Approbation-Humaine-18",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Agent-d-Onboarding-Explication-13": {
      "main": [
        [
          {
            "node": "Traiter-la-Sortie-de-l-Agent-31",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Commiter-vers-GitHub-Approuv--25": {
      "main": [
        [
          {
            "node": "Fusionner-les-Chemins-d-Archiviste-27",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "G-n-rer-le-Contenu-pour-R-vision-15": {
      "main": [
        [
          {
            "node": "G-n-rer-l-ID-de-R-vision-16",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pr-parer-les-Donn-es-de-Contribution-Approuv-es-21": {
      "main": [
        [
          {
            "node": "Sauvegarder-la-Contribution-d-Agent-Approuv-e--22",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sauvegarder-la-Contribution-d-Agent-Approuv-e--22": {
      "main": [
        [
          {
            "node": "G-n-rer-le-Chemin-de-Fichier-GitHub-23",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Avancé - Ingénierie, Intelligence Artificielle

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Informations sur le workflow
Niveau de difficulté
Avancé
Nombre de nœuds35
Catégorie2
Types de nœuds13
Description de la difficulté

Adapté aux utilisateurs avancés, avec des workflows complexes contenant 16+ nœuds

Auteur
Fabrizio Terzi

Fabrizio Terzi

@org

Founder of Pyragogy.org — an open initiative exploring AI–human co-creation, ethical automation, and peer learning ecosystems. I build modular workflows for AI-driven content creation, with a focus on transparency, human-in-the-loop design, and collaborative publishing. Always learning, always building — let’s co-create the future.

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34