--- title: Service Handlers Telegram url: https://blog.guigpap.com/fr/workflows/service-handlers/ url_md: https://blog.guigpap.com/fr/workflows/service-handlers.md category: automation date: '2026-05-19' maturite: production techno: - n8n - telegram - odoo - docker application: - automation - operations --- # Service Handlers Telegram > Famille de 4 sub-workflows N8N qui exécutent les actions Telegram par domaine (General, Odoo, Docker, N8N) ## 1. Quoi ? — Définition et contexte Les **Service Handlers** sont une famille de 4 sub-workflows N8N appelés par l'Orchestrateur Telegram une fois qu'une intention utilisateur a été détectée. Chacun encapsule un domaine fonctionnel (chat général, Odoo, Docker, administration N8N) et expose une interface stable à l'Orchestrateur : on lui passe `{service, action, params, chatId, userId, isAdmin}`, il retourne `{chatId, text, parseMode, keyboard}` prêt à être envoyé à Telegram. > **Note - Pourquoi une famille de handlers** > > L'Orchestrateur Telegram contient déjà la logique de routage, d'authentification et de gestion des callbacks. Y empiler la logique métier de chaque domaine produirait un workflow ingérable. Les Service Handlers découpent par responsabilité : un handler par grande famille d'actions, chacun activable et debuggable indépendamment. ### Composants | Workflow | ID | Nodes | Rôle | |----------|-----|-------|------| | **Service Handler - General** | `eCayuEQg4e8OhW6z` | 10 | Aide (`help`) + chat libre avec Claude (`chat`) | | **Service Handler - Odoo** | `QFvNDnHjV6MIBB7n` | 33 | Contacts, factures, devis, opportunités, projets, taches, dashboard | | **Service Handler - Docker** | `lBrSuWWM2WpN1v9I` | 5 | Dispatcher vers `Docker Actions` + formatage Telegram | | **Service Handler - N8N** (parent) | `lwY2qomOWjrx0EP8` | 6 | Gate admin + délégation au sub-workflow | | **SH N8N Action Executor** (sub) | `HpILpcfY8Z6OJq9b` | 37 | Workflows, executions, datatables, triggers (extrait via #279) | ### Position dans le pipeline Telegram ```mermaid flowchart TD TG["Telegram Trigger"] Orch["Orchestrateur Telegram"] IA["IA Router · détection d'intent"] RBS["Route by Service"] subgraph Handlers["Service Handlers"] direction TB SHG["SH General · 10n"] SHO["SH Odoo · 33n"] SHD["SH Docker · 5n"] SHN["SH N8N · 6n"] end subgraph Subs["Sub-workflows métier"] direction TB Claude["CLI Ollama · /api/generate"] Odoo["13 nodes Odoo natifs"] DA["Docker Actions"] SHNE["SH N8N Action Executor · 37n"] end TG --> Orch --> IA --> RBS RBS -->|general| SHG --> Claude RBS -->|odoo / projet| SHO --> Odoo RBS -->|docker| SHD --> DA RBS -->|n8n| SHN --> SHNE SHG --> Send["Send Telegram Message"] SHO --> Send SHD --> Send SHN --> Send ``` --- ## 2. Pourquoi ? — Enjeux et motivations ### Problèmes résolus | Problème | Sans handlers dédiés | Avec Service Handlers | |----------|---------------------|----------------------| | **Couplage Orchestrateur/métier** | L'Orchestrateur grossit à chaque action ajoutée | Le routage reste ~5 outputs ; le métier vit ailleurs | | **Permissions dispersées** | Check `isAdmin` répété partout | Gate centralisée dans chaque handler sensible | | **Tests cassants** | Tester Odoo nécessite de simuler tout le pipeline Telegram | Sub-workflow appelable isolément avec un payload | | **Évolutivité** | Ajouter `/projet` ou des actions N8N gonfle un workflow déjà critique | Le handler concerné absorbe la croissance | ### Pourquoi extraire SH N8N Action Executor (#279) Le `Service Handler - N8N` faisait initialement 40 nodes : gate admin + 15 actions (list_workflows, toggle, executions, datatables, triggers…). Trois symptômes ont déclenché le refactoring vers un parent fin + sub-workflow lourd : | Symptôme | Cause | |----------|-------| | **Re-tests complets à chaque ajout** | Modifier une action obligeait à retester le gate admin | | **Pagination + callback partout** | La logique pagination était dupliquée entre listes | | **Lecture difficile** | 40 nodes dans un même canvas masquait le Switch d'actions | Le découpage produit 6 nodes en parent (Trigger → Check Admin → Is Admin? → Restore Input → Execute Sub → Return) et 37 nodes dans `SH N8N Action Executor` qui orchestre les 15 actions N8N. ### Pourquoi SH Odoo a grandi à 33 nodes (#187) L'ajout des commandes `/projet` a porté SH Odoo de 27 à 33 nodes : 3 nouveaux Switch outputs (`list_projects`, `create_project`, `project_status`), 5 nodes Odoo natifs supplémentaires, et un Code node d'agrégation pour le dashboard projet. C'est la limite haute supportable avant un découpage analogue à #279 ; cf. section "Et si ?". ### Conventions partagées Tous les handlers respectent les mêmes invariants : | Invariant | Implémentation | |-----------|---------------| | **Trigger unique** | `Execute Workflow Trigger` avec `inputSource: passthrough` | | **Send Typing** | Indicateur d'activité Telegram pendant l'attente | | **Format Response** | Sortie standard `{chatId, text, parseMode, keyboard.pattern}` | | **Long message** | Split / pagination si > 4096 caractères | | **`onError: continueRegularOutput`** | Sur les nodes externes (Odoo, HTTP) pour formatter les erreurs proprement | --- ## 3. Comment ? — Mise en œuvre technique ### Tableau récapitulatif | Workflow | ID | Nodes | Rôle | |----------|-----|-------|------| | Service Handler - General | `eCayuEQg4e8OhW6z` | 10 | Help statique + chat Claude via CLI Ollama | | Service Handler - Odoo | `QFvNDnHjV6MIBB7n` | 33 | 13 actions Odoo (contacts, factures, projets, dashboard) | | Service Handler - Docker | `lBrSuWWM2WpN1v9I` | 5 | Dispatch vers `Docker Actions` + keyboards contextuels | | Service Handler - N8N | `lwY2qomOWjrx0EP8` | 6 | Gate admin + appel sub-workflow | | SH N8N Action Executor | `HpILpcfY8Z6OJq9b` | 37 | Exécution réelle des actions N8N (extrait #279) | ### Flux interne par handler ```mermaid flowchart TD Trigger["Execute Workflow Trigger
service · action · params · chatId · isAdmin"] subgraph General["SH General · 10n"] direction TB Typing1["Send Typing"] Route1["Route by Action"] Help["Generate Help Msg"] Chat["Call Claude /api/generate"] Fmt1["Format Response"] end subgraph Odoo["SH Odoo · 33n"] direction TB AdminO["Check Admin"] SwitchO["Route by Action · 13 outputs"] Search["3x Search Contact + Merge + Dedup"] IfInv["IF Invoice Status"] SwPer["Switch Period · week/month/all"] PStat["3 Odoo queries + Aggregate"] Fmt2["Format Response"] end subgraph Docker["SH Docker · 5n"] direction TB Parse["Parse Docker Params"] CallDA["Call Docker Actions"] Fmt3["Format Response · keyboard pattern"] end subgraph N8N["SH N8N · 6n"] direction TB AdminN["Check Admin · DT GET"] IsAdm["Is Admin?"] Restore["Restore Input Data"] CallExec["Call SH N8N Action Executor · 37n"] end Trigger --> General Trigger --> Odoo Trigger --> Docker Trigger --> N8N ``` ### Gate admin (Odoo & N8N) Les actions Odoo et N8N sont sensibles (mutation de données, exécution de workflows). Les deux handlers interrogent la même Data Table `Telegram Authorized users` (`invf2IKyVDyzoBZr`) et coupent net en cas d'absence du flag. | Étape | Comportement | |-------|-------------| | Lookup `Telegram Authorized users` par `telegram_user_id` | Récupère la ligne utilisateur | | `is_admin` absent ou `false` | Sortie immédiate avec `⛔ Accès réservé aux administrateurs.` | | `is_admin === true` | Continue le flux métier | | Réponse non-admin | `parseMode: HTML`, `keyboard.pattern: no_keyboard` | > **Caution - Toujours coupler gate + Restore Input** > > Le node Data Table GET du `Check Admin` écrase `$json` avec les colonnes de la table. Sans un node `Restore Input Data` qui réinjecte `$('Execute Workflow Trigger').first().json`, les nodes en aval perdent `service`, `action`, `params`, etc. Bug observé pendant #279 puis corrigé. ### Service Handler - General · 10 nodes Deux actions : `help` (réponse statique HTML), et `chat` (fallback, appel CLI Ollama `/api/generate` avec `model: codex-yolo`, `session_id` généré). Le body de l'appel est sérialisé via `JSON.stringify` pour échapper guillemets et retours ligne — sans quoi un texte issu de Gemini Vision OCR avec citations cassait le payload (correctif `d80e5ce`). ### Service Handler - Odoo · 33 nodes Le handler implémente 13 actions via un Switch à 13 outputs + fallback : | Action | Modèle Odoo | Particularité | |--------|-------------|---------------| | `search_contact` | `res.partner` | 3 recherches parallèles (name / email / phone) + Merge + Dedup | | `create_contact` / `update_contact` | `res.partner` | Update via Custom Resource avec champ dynamique | | `search_invoice` | `account.move` | IF conditionnel sur `payment_state` | | `search_quote` / `search_opportunity` | `sale.order` / `crm.lead` | Filtres simples | | `create_opportunity` | `crm.lead` | Champs : name, email, phone, revenue, probability | | `project_hours` | `account.analytic.line` | Switch Period (week / month / all) | | `search_project` / `search_task` | `project.project` / `project.task` | Custom Resource + filtre `name like` | | `list_projects` | `project.project` | Tous les projets actifs (limit 15) | | `create_project` | `project.project` | Via `/projet create ` | | `project_status` | 3 modèles | Chaîne : Get Project → Tasks → Timesheets → Aggregate Code | Tous les nodes Odoo utilisent le credential `Odoo tool admin` (`xQmHuksl8E7nuwCV`), `onError: continueRegularOutput`, et `alwaysOutputData: true` (top-level) pour que `Format Response` s'exécute même sans résultat. ### Service Handler - Docker · 5 nodes Le handler reste mince car le travail réel est délégué à `Docker Actions` (SSH vers le VPS, exécution de `docker compose`). Le node `Parse Docker Params` traduit l'intent IA Router en paire `(dockerAction, dockerStack)` : - Passthrough si l'action est déjà valide (`status`, `restart`, `logs`, `update`, `start`, `stop`, `list-all`). - Sinon mapping via `ACTION_MAP` (`container_status` → `status`, `restart_container` → `restart`…). - Mapping container → stack via `CONTAINER_TO_STACK` (`n8n` → `n8n-stack`, `caddy` → `security-stack`…). - Erreur explicite si action inconnue (au lieu d'un fallback silencieux sur `status`). `Format Response` produit `keyboard.pattern` parmi `standard` / `critical` / `list-all` / `after-action`. L'Orchestrateur applique ensuite le bon Switch côté envoi (cf. règle « Reply Markup non expressionable »). | Pattern | Usage | Boutons | |---------|-------|---------| | `standard` | Stack non-critique après status/logs | Status · Restart · Logs · Update · Retour | | `critical` | `security-stack` (restart/stop/start/update bloqués) | Status · Logs · Retour | | `list-all` | Vue tous containers | Status… · Restart… · Logs… · Retour | | `after-action` | Après restart/update | Status · Logs · Restart · Retour | ### Service Handler - N8N · 6 nodes + sub-workflow 37 nodes Le parent ne fait que (1) gate admin via Data Table, (2) restore input, (3) déléguer à `SH N8N Action Executor` via Execute Workflow. Le sub-workflow expose 15 actions : | Famille | Actions | |---------|---------| | **Workflows** | `list_workflows` (paginé), `toggle_workflow` | | **Executions** | `list_running`, `list_executions` (filtres status), `view_execution`, `retry_execution` | | **Data Tables** | `list_datatables`, `view_datatable` (paginé), `view_row`, `edit_row`, `delete_row` | | **Triggers manuels** | `list_triggers`, `list_trigger_cat`, `confirm_trigger`, `execute_trigger` | Les IDs longs (>64 octets pour les callbacks Telegram) sont stockés dans la Data Table `n8n_pending_actions` avec un short ID 8 chars et un TTL de 5 minutes. Les workflows déclenchables manuellement sont déclarés dans `n8n_trigger_whitelist` (catégories `backup` / `sync` / `cleanup` / `maintenance`). ### Long response handling Telegram impose 4096 caractères par message. Trois patterns coexistent : | Pattern | Quand l'utiliser | |---------|------------------| | **Troncature** | Listes simples, dernier message contient `… N résultats` | | **Pagination** | Listes longues type executions ou data tables — boutons `◀️ N/M ▶️` | | **Multi-message** | Détails d'exécution avec stack trace — `multiMessage: true`, keyboard sur le dernier seulement | ### Data Tables référencées | Data Table | ID | Utilisée par | |------------|-----|--------------| | `Telegram Authorized users` | `invf2IKyVDyzoBZr` | SH Odoo, SH N8N (gate admin) | | `n8n_trigger_whitelist` | — | SH N8N Action Executor (triggers manuels) | | `n8n_pending_actions` | — | SH N8N Action Executor (short ID ↔ payload) | | `n8n_datatable_metadata` | — | SH N8N Action Executor (tables exposées) | ### Interface d'entrée / sortie standard **Entrée (depuis l'Orchestrateur) :** ```json { "service": "odoo", "action": "search_contact", "params": { "query": "Dupont" }, "chatId": 123456789, "userId": 123456789, "isAdmin": true, "originalText": "Cherche le contact Dupont" } ``` **Sortie (vers l'Orchestrateur) :** ```json { "chatId": 123456789, "text": "📇 Contacts (2 résultats)...", "parseMode": "HTML", "keyboard": { "pattern": "odoo_contacts" } } ``` L'Orchestrateur ne lit que `text`, `parseMode`, et `keyboard.pattern` pour router vers le bon node Telegram (la structure du Reply Markup est hardcodée par pattern dans l'UI — N8N ne supporte pas les expressions sur la structure du keyboard, seulement sur les valeurs des boutons). --- ## 4. Et si ? — Perspectives et limites ### Limites actuelles | Limite | Impact | Mitigation | |--------|--------|-----------| | **SH Odoo à 33 nodes** | Lecture du canvas devient pénible | Découpage prévu (par famille d'actions) sur le modèle de #279 | | **SH General sans mémoire** | Le chat libre n'a pas d'historique multi-tours | Conversation Agent dédié pour les conversations multi-turn | | **SH Docker dépend de SSH** | Toute panne SSH bloque toutes les actions Docker | `Docker Actions` retourne success/error, le handler formate l'erreur | | **Gate admin = column lookup** | Pas de RBAC fin (juste admin / non-admin) | Suffisant pour usage solo, à élargir si multi-utilisateurs | | **`Reply Markup` non expressionable** | Chaque pattern nécessite un node Telegram dédié | Pattern Switch + keyboards hardcodés (cf. doc telegram orchestrator) | ### Scénarios d'évolution **Si SH Odoo dépasse 40 nodes** : - Découper par famille (`SH Odoo Contacts`, `SH Odoo Projects`, `SH Odoo Sales`) sur le modèle #279 - Garder le parent comme dispatcher de gate admin + Switch initial - Bénéfice : tests indépendants, ajout d'actions sans toucher au reste **Si besoin de permissions plus fines** : - Ajouter des colonnes à `Telegram Authorized users` (`can_docker`, `can_odoo`, `can_n8n`) - Remplacer le check binaire `is_admin` par un check capability-based dans chaque handler - Garder la rétrocompatibilité : `is_admin = true` implique toutes les capacités **Si volume de chat Claude augmente** : - SH General reste single-turn (utilité limitée pour conversation continue) - Router proactivement vers Conversation Agent au-delà de N messages dans la même fenêtre - Cache de session côté CLI Ollama via Redis (déjà en place) **Si nouveau domaine (ex. monitoring direct)** : - Créer `Service Handler - Monitoring` (Prometheus queries, Grafana dashboards) - Ajouter un output `monitoring` dans Route by Service de l'Orchestrateur - L'IA Router apprend la nouvelle catégorie via examples dans son prompt > **Tip - Critère de découpage** > > On extrait un sub-workflow quand (1) la lisibilité du canvas devient bloquante, (2) une partie du flux a sa propre logique de retry / pagination, ou (3) les tests deviennent couplés à l'ensemble. SH N8N a franchi les trois critères et a été refactorisé en #279. --- ## Pages liées ### Workflows - [Telegram Orchestrator](/fr/workflows/telegram-orchestrator/) — Appelant des Service Handlers - [IA Router & MCP](/fr/workflows/ia-router-mcp/) — Détection d'intent en amont - [Notification Hub](/fr/workflows/notification-hub/) — Pattern de routage analogue côté sortie - [Docker Updates](/fr/workflows/docker-updates/) — Consommateur du flux Docker - [GitHub-Odoo Sync](/fr/workflows/github-odoo-sync/) — Autre famille de sub-workflows extraits ### Infrastructure - [Database Backup](/fr/infrastructure/database-backup/) — Sauvegardes consultables via SH N8N triggers ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0