Flux / JevModel

Classification de messages Slack : router les messages de canal avec l’Events API et Jev

Classez les messages d’un canal Slack d’aide ou de feedback avec une petite app : recevez les événements message.channels, vérifiez la signature, accusez réception en moins de 3 secondes, puis demandez à Jev un Choice fixe et un Noul d’urgence pour réagir, répondre ou transférer. Avec relances, déduplication, confidentialité et quand un outil no-code suffit.

Dernière mise à jour:

Réponse courte : accuser réception d’abord, classer ensuite

  • Une app Slack abonnée à l’événement bot message.channels (scope channels:history) reçoit un HTTP POST par message dans les canaux publics que le bot a rejoints. Vérifiez l’en-tête X-Slack-Signature avec votre signing secret, puis renvoyez HTTP 200 en moins de 3 secondes ; Slack considère les réponses plus lentes comme des échecs et relance jusqu’à 3 fois.
  • Après l’accusé de réception, envoyez uniquement le texte du message à /api/v1/systemone de JevModel avec un Choice de 2 à 12 labels que vous définissez (par exemple bug, access, how_to, other) et un Noul « bloqué maintenant ? ». Les probabilités renvoyées décident d’un emoji, d’une réponse en fil ou d’un transfert ; les cas incertains vont à une personne.
  • Dédupliquez sur event_id, ignorez les messages de bots et les éditions, et ne supprimez jamais un message à cause d’un label. Si vous utilisez déjà Zapier, Make, n8n, Power Automate ou Pipedream, leurs déclencheurs Slack peuvent appeler la même API Jev sans héberger de serveur.

Que signifie concrètement classer des messages Slack ?

La plupart des équipes ont au moins un canal où s’accumulent des demandes hétérogènes : #help, #it-support, #customer-feedback, #sales-questions. Classer, c’est lire chaque nouveau message et lui attribuer un label d’une liste courte et fixe, pour qu’il atteigne la bonne personne sans que quelqu’un surveille le canal toute la journée. Le label n’est utile que s’il déclenche quelque chose de visible : une réaction emoji sur laquelle filtrent les responsables, une réponse en fil qui demande les informations manquantes, une copie transférée vers un canal d’équipe ou un ticket dans votre système. Une seconde question indépendante compte souvent autant que la catégorie : quelqu’un est-il bloqué maintenant ? Gardez ces deux décisions séparées. Un message peut être une question d’usage peu prioritaire ou un problème d’accès urgent ; mêler l’urgence à la liste de catégories dégrade les deux réponses. Ce guide couvre les canaux publics rejoints par un bot. Messages directs et canaux privés exigent d’autres scopes et une justification de confidentialité plus forte ; ils sont hors sujet ici.

Route customer support with JevClassification d’intention pour texte produit et support : règles, LLM et API typéesChoice, Score, and Noul

Comment une app Slack reçoit-elle les messages d’un canal ?

Utilisez l’Events API. Dans les réglages de l’app, activez Event Subscriptions et choisissez la livraison : une Request URL HTTP vers laquelle Slack envoie des POST, ou Socket Mode, où votre processus ouvre un WebSocket sans URL publique. Dans les bot events, abonnez-vous à message.channels ; il exige le scope channels:history et vous ne recevez que les messages des canaux publics dont le bot est membre : invitez-le avec /invite dans chaque canal à classer. Chaque événement arrive en JSON avec une enveloppe externe (type event_callback, team_id, un event_id unique au niveau global) et un événement interne portant type message, channel, user, text, ts et channel_type channel. Pour ne traiter que les mentions du bot, abonnez-vous plutôt à app_mention. En livraison HTTP, Slack signe chaque requête : calculez v0= suivi du condensé hexadécimal HMAC-SHA256 de la chaîne v0:{X-Slack-Request-Timestamp}:{corps brut} avec votre signing secret, comparez-le à X-Slack-Signature en temps constant et rejetez les horodatages à plus de cinq minutes de votre horloge pour bloquer les rejeux. Bolt pour JavaScript, Python et Java le fait pour vous. La première requête vers une nouvelle Request URL est un défi url_verification : renvoyez la valeur challenge.

JevModel API: request and responsePrivate runs and history

Pourquoi l’app doit-elle répondre à Slack en 3 secondes ?

La documentation de l’Events API de Slack demande un HTTP 2xx sous trois secondes ; sinon la livraison est un échec et Slack relance trois fois : presque immédiatement, après une minute et après cinq minutes, avec un en-tête x-slack-retry-num (1, 2, 3) et un x-slack-retry-reason comme http_timeout. Si plus de 95 % des livraisons échouent en 60 minutes, Slack peut désactiver temporairement vos abonnements (les apps recevant moins de 1 000 événements par heure ne sont pas désactivées automatiquement). Un appel de classification est une requête réseau vers un autre service : ne faites pas attendre Slack. Accusez réception d’abord, puis classez depuis une file ou une tâche de fond, comme Slack le recommande. Trois règles évitent doublons et boucles. Un : conservez l’event_id de chaque événement traité et ignorez les répétitions ; une relance porte le même event_id et l’API JevModel n’a pas de clé d’idempotence, donc une livraison en double serait classée et facturée deux fois. Deux : ignorez les messages avec bot_id ou subtype (éditions, arrivées, réponses de votre propre bot), sinon le bot classera ses propres réponses. Trois : prévoyez le volume : Slack plafonne les livraisons à 30 000 événements par espace de travail et par app sur 60 minutes, et chaque message classé est une requête Jev ordinaire sur votre solde de tokens.

JevModel pricing and free Jev runsJevModel API: request and response

Comparaison : déclencheur Slack no-code vs votre propre app Slack vs export CSV

Les trois peuvent envoyer la même question Jev ; ils diffèrent par qui gère la tuyauterie. Le tableau compare la forme du flux, pas la précision, et aucune ligne n’est un benchmark.

Classification de texte Zapier : AI by Zapier Classify vs Vertex Classify Text vs HTTP → JevNœud Text Classifier n8n : intégré vs appeler Jev depuis un workflowBatch text classification with the JevModel API
Comparaison : déclencheur Slack no-code vs votre propre app Slack vs export CSV
DimensionOutil no-code (Zapier, Make, n8n…)App maison → Events API → JevExport du canal → Console → Batch
HébergementLe déclencheur Slack de l’outil ; aucun serveurVotre endpoint ou un processus Socket ModeNavigateur seulement
DélaiDépend du polling ou webhook de l’outilQuasi temps réel après l’accusé de 3 sA posteriori, pour l’analyse
Relances et déduplicationGérés par l’outil ; voir son historiqueÀ vous : stocker event_id, ignorer bot_id et subtypeSans objet
Sortie JevMême réponse Choice / Noul avec probabilitésIdem, avec contrôle total des réactions et réponsesRéponses par ligne dans l’export (50 lignes par lot avant achat, 1 000 ensuite)

Exemple de code : vérifier, accuser réception, classer, réagir (illustratif)

L’esquisse est un handler HTTP Node 18+ sans framework pour que chaque étape soit visible ; Bolt peut gérer la signature et l’accusé de réception. Donnez au bot les scopes channels:history et reactions:write, abonnez-vous à message.channels et invitez le bot dans le canal. Le handler vérifie la signature sur le corps brut, répond à url_verification, renvoie 200 tout de suite et seulement ensuite appelle Jev. La requête suit le contrat documenté de JevModel : POST /api/v1/systemone avec une clé Bearer d’un compte vérifié, un state et des questions nommées, ici un Choice pour le type de demande et un Noul pour l’urgence ; une réponse réussie a code 0 et data.answers. Pas de champ model, car le site appelle toujours typesafe/jev-1.13 via OpenRouter ; JevModel est indépendant de TypeSafe AI, qui développe Jev. Les réponses peu probables reçoivent la réaction eyes pour qu’une personne regarde, et un code non nul laisse le message intact. Le Set en mémoire ne sert qu’à l’exemple ; utilisez un stockage partagé avec plusieurs processus, et une vraie file si votre plateforme arrête le travail après l’envoi de la réponse.

JevModel API: request and responseChoice, Score, and NoulJev probability, confidence and human-review thresholds
// Illustrative only. Node 18+. Env: SLACK_SIGNING_SECRET, SLACK_BOT_TOKEN, JEVMODEL_API_KEY.
// Bot scopes: channels:history (for message.channels) and reactions:write.
import crypto from "node:crypto";
import http from "node:http";

const handled = new Set(); // event_id values; use a shared store in production

function fromSlack(req, raw) {
  const ts = Number(req.headers["x-slack-request-timestamp"]);
  if (!ts || Math.abs(Date.now() / 1000 - ts) > 300) return false; // replay window
  const mine = "v0=" + crypto
    .createHmac("sha256", process.env.SLACK_SIGNING_SECRET)
    .update("v0:" + ts + ":" + raw)
    .digest("hex");
  const theirs = String(req.headers["x-slack-signature"] || "");
  return mine.length === theirs.length &&
    crypto.timingSafeEqual(Buffer.from(mine), Buffer.from(theirs));
}

http.createServer((req, res) => {
  let raw = "";
  req.on("data", (chunk) => (raw += chunk));
  req.on("end", () => {
    if (!fromSlack(req, raw)) return res.writeHead(401).end();
    const body = JSON.parse(raw);
    if (body.type === "url_verification") return res.end(body.challenge);
    res.writeHead(200).end(); // acknowledge within 3 seconds, work afterwards
    const e = body.event;
    if (!e || e.type !== "message" || e.subtype || e.bot_id) return; // skip edits, joins, bots
    if (handled.has(body.event_id)) return; // Slack retries reuse the event_id
    handled.add(body.event_id);
    classify(e).catch(console.error);
  });
}).listen(3000);

async function classify(e) {
  const res = await fetch("https://jevmodel.app/api/v1/systemone", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + process.env.JEVMODEL_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      state: { channel: "help", text: e.text }, // text only, no user profile
      questions: {
        kind: {
          type: "choice",
          instructions: "What kind of request is this help-channel message?",
          criteria: {
            bug: "Something is broken, slow, or returns an error.",
            access: "Asking for an account, a permission, or access to a tool.",
            how_to: "Asking how to do something that already works.",
            other: "Chat, thanks, or anything that fits none of the labels above.",
          },
        },
        urgent: {
          type: "noul",
          instructions: "Is someone blocked right now by what this message describes?",
          criteria: {
            true: "A person or customer cannot work now, or a service is down.",
            false: "It can wait for normal working hours.",
          },
        },
      },
    }),
  });
  const out = await res.json();
  if (out.code !== 0) return; // leave the message unlabeled
  const { kind, urgent } = out.data.answers;
  const p = kind.probabilities ? kind.probabilities[kind.choice] : 0;
  const emoji = { bug: "bug", access: "key", how_to: "question" };
  const names = [p < 0.7 ? "eyes" : emoji[kind.choice]]; // 0.7 is a placeholder
  if (urgent.noul >= 0.8) names.push("rotating_light"); // placeholder threshold
  for (const name of names.filter(Boolean)) {
    await fetch("https://slack.com/api/reactions.add", {
      method: "POST",
      headers: {
        Authorization: "Bearer " + process.env.SLACK_BOT_TOKEN,
        "Content-Type": "application/json; charset=utf-8",
      },
      body: JSON.stringify({ channel: e.channel, timestamp: e.ts, name }),
    });
  }
}

Que doit-il se passer une fois le message étiqueté ?

Préférez des actions visibles et réversibles. La réaction est la plus légère : les responsables filtrent par emoji et n’importe qui peut retirer une erreur. Une réponse en fil qui demande une capture ou un numéro de commande est utile pour how_to ou bug, mais publiez-la seulement au-dessus d’un seuil testé, car une réponse automatique erronée dans un canal chargé dérange. Transférer un lien vers un canal d’équipe ou ouvrir un ticket dans votre système convient une fois le label fiable. Ne supprimez, ne masquez ni ne déplacez de messages à cause d’un label, et n’alertez pas l’astreinte sur le seul Noul avant d’avoir mesuré sa fréquence d’erreur. N’envoyez que l’utile : le texte suffit en général ; laissez de côté noms, e-mails et champs de profil. Avant l’activation, exportez quelques centaines de messages passés du canal, étiquetez-les à la main, lancez les mêmes questions et comparez par label ; fixez les seuils sur cet échantillon. 0,7 et 0,8 dans l’exemple sont des valeurs d’attente, pas des recommandations. Surveillez la part de other et de réactions eyes : une part élevée signale des définitions à revoir.

Evaluate a Jev workflowJev probability, confidence and human-review thresholdsPrivate runs and history

Quelle configuration choisir ?

Choisissez un outil no-code s’il exécute déjà vos autres automatisations et qu’un délai d’environ une minute convient ; les guides Zapier, Make, n8n, Power Automate et Pipedream montrent la même requête Jev dans chaque outil. Choisissez votre propre app Slack pour des réactions quasi temps réel, un contrôle total des relances et réponses, ou plusieurs canaux aux jeux de labels différents. Choisissez l’export du canal puis Console → Batch pour une analyse ponctuelle de l’usage d’un canal, pas pour du routage en direct. Checklist commune : 1) définitions de labels avec un other explicite ; 2) urgence dans un Noul séparé, pas dans un label ; 3) déduplication sur event_id et messages de bots ignorés ; 4) envoyer le texte, pas le profil ; 5) commencer par les réactions seules, puis ajouter réponses ou tickets après avoir mesuré l’accord sur un échantillon étiqueté. JevModel est limité au texte et n’est pas open source ; images, fichiers et messages vocaux Slack ne sont pas classés par ce montage. Si un message peut relever de plusieurs catégories à la fois, lisez multi-label-text-classification avant d’élargir le Choice.

Classification de texte multi-label : un Choice, plusieurs questions oui/non, ou une seconde passeCategorize Text sur Make.com : AI Toolkit vs HTTP → Jev pour labels typés et seuilsClassification de texte Power Automate : modèles AI Builder vs HTTP → JevClassification de texte Pipedream : action Hugging Face vs HTTP → JevJev est-il multimodal / téléchargeable ? Ce qu’est vraiment JevModelÉtiqueter automatiquement les issues GitHub avec l’IA : un workflow GitHub Actions qui appelle JevClassification d’e-mails par IA : règles, prompts LLM ou API de décision typée pour le courrier entrant

Questions fréquentes

Faut-il un serveur public pour classer des messages Slack ?

Non. Avec Socket Mode, votre processus ouvre un WebSocket vers Slack et reçoit les mêmes événements sans Request URL publique. Un outil no-code doté d’un déclencheur Slack est une autre option sans serveur à vous.

Pourquoi le même message Slack a-t-il été classé deux fois ?

En général parce que l’app a répondu à Slack après plus de 3 secondes et que Slack a relancé avec le même event_id. Accusez réception tout de suite, stockez les event_id traités et ignorez les répétitions ; JevModel n’a pas de clé d’idempotence pour le faire à votre place.

Le bot peut-il classer des canaux privés ou des messages directs ?

Seulement avec les événements de message et scopes history propres à ces types de conversation, et seulement là où le bot est membre. Ce guide couvre les canaux publics ; pour le privé, obtenez l’accord explicite des responsables de l’espace de travail et n’envoyez que le texte minimal nécessaire.

Combien coûte la classification de messages Slack avec Jev ?

Chaque message classé est une requête API ordinaire facturée en tokens sur votre solde ; les comptes vérifiés démarrent avec 100 000 tokens offerts, et le Choice et le Noul d’une même requête sont facturés ensemble. Filtrer messages de bots, éditions et bavardages très courts avant l’appel réduit l’usage. Voir le guide des tarifs pour les packs actuels.

JevModel est indépendant et n’est pas affilié à TypeSafe AI.