Flujos / JevModel

Clasificación de mensajes de Slack: enruta mensajes de canal con la Events API y Jev

Clasifica mensajes de un canal de ayuda o feedback de Slack con una app pequeña: recibe eventos message.channels, verifica la firma, responde en menos de 3 segundos y luego pide a Jev un Choice fijo y un Noul de urgencia para reaccionar, responder o reenviar. Incluye reintentos, deduplicación, privacidad y cuándo basta una herramienta no-code.

Última actualización:

Respuesta breve: primero confirma, después clasifica

  • Una app de Slack suscrita al evento de bot message.channels (scope channels:history) recibe un HTTP POST por cada mensaje en los canales públicos a los que se unió el bot. Verifica la cabecera X-Slack-Signature con tu signing secret y devuelve HTTP 200 en menos de 3 segundos; Slack considera fallidas las respuestas más lentas y reintenta hasta 3 veces.
  • Tras confirmar, envía solo el texto del mensaje a /api/v1/systemone de JevModel con un Choice de 2–12 labels que tú defines (por ejemplo bug, access, how_to, other) y un Noul de “¿bloqueado ahora?”. Usa las probabilidades devueltas para reaccionar con un emoji, responder en hilo o reenviar, y pasa los dudosos a una persona.
  • Deduplica por event_id, ignora mensajes de bots y ediciones, y nunca borres un mensaje por un label. Si ya usas Zapier, Make, n8n, Power Automate o Pipedream, sus triggers de Slack pueden llamar a la misma API de Jev sin alojar un servidor.

¿Qué significa en la práctica clasificar mensajes de Slack?

Casi todos los equipos tienen al menos un canal donde se acumulan peticiones dispares: #help, #it-support, #customer-feedback, #sales-questions. Clasificar significa leer cada mensaje nuevo y asignarle un label de una lista corta y fija, para que llegue a la persona adecuada sin que alguien vigile el canal todo el día. El label solo sirve si dispara algo visible: una reacción con emoji por la que filtran los responsables, una respuesta en hilo que pide datos que faltan, una copia reenviada a un canal de equipo o un ticket en tu sistema. Una segunda pregunta independiente suele importar tanto como la categoría: ¿alguien está bloqueado ahora? Mantén separadas ambas decisiones. Un mensaje puede ser una duda de uso poco prioritaria o un problema de acceso urgente, y mezclar la urgencia en la lista de categorías empeora las dos respuestas. Esta guía cubre canales públicos a los que se ha unido un bot. Los mensajes directos y los canales privados requieren otros scopes y una justificación de privacidad más fuerte; quedan fuera.

Route customer support with JevClasificación de intención en texto de producto y soporte: reglas, LLM y APIs tipadasChoice, Score, and Noul

¿Cómo recibe una app de Slack los mensajes de un canal?

Usa la Events API. En la configuración de la app activa Event Subscriptions y elige cómo recibir: una Request URL HTTP a la que Slack hace POST, o Socket Mode, donde tu proceso abre un WebSocket y no necesita URL pública. En bot events suscríbete a message.channels; requiere el scope channels:history y solo recibes mensajes de canales públicos de los que el bot es miembro, así que invítalo con /invite en cada canal que quieras clasificar. Cada evento llega en JSON con un sobre exterior (type event_callback, team_id y un event_id único global) y un evento interior con type message, channel, user, text, ts y channel_type channel. Si solo quieres mensajes que mencionan al bot, suscríbete a app_mention. Con entrega HTTP, Slack firma cada petición: calcula v0= más el digest hex HMAC-SHA256 de la cadena v0:{X-Slack-Request-Timestamp}:{cuerpo sin procesar} con tu signing secret, compáralo con X-Slack-Signature en tiempo constante y rechaza marcas de tiempo a más de cinco minutos de tu reloj para evitar repeticiones. Bolt para JavaScript, Python y Java lo hace por ti. La primera petición a una Request URL nueva es un reto url_verification que respondes devolviendo el valor challenge.

JevModel API: request and responsePrivate runs and history

¿Por qué la app debe responder a Slack en 3 segundos?

La documentación de la Events API de Slack pide un HTTP 2xx en menos de tres segundos; si no, la entrega cuenta como fallida y Slack reintenta tres veces: casi de inmediato, al minuto y a los cinco minutos, con una cabecera x-slack-retry-num (1, 2, 3) y un x-slack-retry-reason como http_timeout. Si falla más del 95% de las entregas en 60 minutos, Slack puede desactivar temporalmente tus suscripciones (las apps con menos de 1.000 eventos por hora no se desactivan automáticamente). Una clasificación es una petición de red a otro servicio, así que no hagas esperar a Slack. Confirma primero y clasifica después desde una cola o tarea en segundo plano, como recomienda la propia Slack. Tres reglas más evitan trabajo doble y bucles. Primera: guarda el event_id de cada evento procesado y descarta repetidos; un reintento lleva el mismo event_id y la API de JevModel no tiene clave de idempotencia, así que una entrega duplicada se clasificaría y cobraría dos veces. Segunda: ignora mensajes con bot_id o subtype (ediciones, uniones, respuestas de tu propio bot), o el bot clasificará sus propias respuestas. Tercera: presupuesta el volumen: Slack limita las entregas a 30.000 eventos por workspace y app cada 60 minutos, y cada mensaje clasificado es una petición Jev normal contra tu saldo de tokens.

JevModel pricing and free Jev runsJevModel API: request and response

Comparación: trigger de Slack no-code vs app de Slack propia vs exportar CSV

Los tres pueden enviar la misma pregunta Jev; cambia quién mantiene la fontanería. La tabla compara la forma del flujo, no la precisión, y ninguna fila es un benchmark.

Clasificación de texto en Zapier: AI by Zapier Classify vs Vertex Classify Text vs HTTP → JevNodo Text Classifier de n8n: integrado vs llamar a Jev desde un flujoBatch text classification with the JevModel API
Comparación: trigger de Slack no-code vs app de Slack propia vs exportar CSV
DimensiónHerramienta no-code (Zapier, Make, n8n…)App propia → Events API → JevExportar canal → Console → Batch
AlojamientoEl trigger de Slack de la herramienta; sin servidorTu endpoint o un proceso Socket ModeSolo el navegador
TiempoDepende del sondeo o webhook de la herramientaCasi en tiempo real tras la confirmación de 3 sA posteriori, para análisis
Reintentos y deduplicaciónLo gestiona la herramienta; revisa su historialTuyos: guardar event_id, omitir bot_id y subtypeNo aplica
Salida de JevLa misma respuesta Choice / Noul con probabilidadesIgual, con control total de reacciones y respuestasRespuestas por fila en la exportación (50 filas por lote antes de comprar, 1.000 después)

Ejemplo de código: verificar, confirmar, clasificar, reaccionar (ilustrativo)

El esbozo es un handler HTTP de Node 18+ sin framework para que se vea cada paso; Bolt puede encargarse de la firma y la confirmación. Da al bot los scopes channels:history y reactions:write, suscríbete a message.channels e invita al bot al canal. El handler verifica la firma sobre el cuerpo sin procesar, responde url_verification, devuelve 200 al instante y solo entonces llama a Jev. La petición sigue el contrato documentado de JevModel: POST /api/v1/systemone con una clave Bearer de una cuenta verificada, un state y preguntas con nombre, aquí un Choice para el tipo de petición y un Noul para la urgencia; una respuesta correcta trae code 0 y data.answers. No hay campo model porque el sitio siempre llama a typesafe/jev-1.13 vía OpenRouter; JevModel es independiente de TypeSafe AI, que desarrolla Jev. Las respuestas de baja probabilidad reciben la reacción eyes para que mire una persona, y un code distinto de cero deja el mensaje intacto. El Set en memoria es solo para el ejemplo; usa un almacén compartido si corres varios procesos y una cola real si tu plataforma detiene el trabajo tras enviar la respuesta.

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 }),
    });
  }
}

¿Qué debe pasar después de etiquetar un mensaje?

Prefiere acciones visibles y reversibles. La reacción es la más ligera: los responsables filtran por emoji y cualquiera puede quitar una equivocada. Una respuesta en hilo que pide una captura o un número de pedido es útil para how_to o bug, pero publícala solo por encima de un umbral probado, porque una respuesta automática errónea en un canal concurrido molesta. Reenviar un enlace a un canal de equipo o abrir un ticket en tu sistema está bien cuando el label sea fiable. No borres, ocultes ni muevas mensajes por un label, ni avises a la guardia solo con el Noul hasta saber con qué frecuencia se equivoca. Envía solo lo que la decisión necesita: el texto suele bastar, así que omite nombres, emails y campos de perfil. Antes de activarlo, exporta unos cientos de mensajes pasados del canal, etiquétalos a mano, ejecuta las mismas preguntas y compara por label; fija los umbrales con esa muestra. 0,7 y 0,8 en el ejemplo son marcadores, no recomendaciones. Vigila la proporción de other y de reacciones eyes: si es alta, las definiciones necesitan trabajo.

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

¿Qué configuración elegir?

Elige una herramienta no-code si ya ejecuta tus otras automatizaciones y un retraso de un minuto aproximado es aceptable; las guías de Zapier, Make, n8n, Power Automate y Pipedream muestran la misma petición Jev en cada una. Elige tu propia app de Slack si quieres reacciones casi en tiempo real, control total de reintentos y respuestas, o varios canales con conjuntos de labels distintos. Elige exportar el canal y usar Console → Batch si el objetivo es un análisis puntual de para qué se usó un canal, no el enrutado en vivo. Lista para cualquiera: 1) define labels con un other explícito; 2) deja la urgencia como Noul aparte, no como label; 3) deduplica por event_id e ignora mensajes de bots; 4) envía el texto, no el perfil; 5) empieza solo con reacciones y añade respuestas o tickets tras medir la concordancia en una muestra etiquetada. JevModel es solo texto y no es de código abierto; imágenes, archivos y audios de Slack no se clasifican con esta configuración. Si un mensaje puede pertenecer a varias categorías a la vez, lee multi-label-text-classification antes de ampliar el Choice.

Clasificación de texto multietiqueta: un Choice, varias preguntas sí/no o una segunda pasadaCategorize Text en Make.com: AI Toolkit vs HTTP → Jev para labels tipados y umbralesClasificación de texto en Power Automate: modelos AI Builder vs HTTP → JevClasificación de texto en Pipedream: acción Hugging Face vs HTTP → Jev¿Jev es un modelo multimodal o descargable? Qué es realmente JevModelEtiquetar issues de GitHub con IA: un workflow de GitHub Actions que llama a JevClasificación de emails con IA: reglas, prompts LLM o una API de decisión tipada para el correo entrante

Preguntas frecuentes

¿Necesito un servidor público para clasificar mensajes de Slack?

No. Con Socket Mode tu proceso abre un WebSocket hacia Slack y recibe los mismos eventos sin Request URL pública. Una herramienta no-code con trigger de Slack es otra opción sin servidor propio.

¿Por qué se clasificó dos veces el mismo mensaje de Slack?

Normalmente porque la app respondió a Slack tras más de 3 segundos y Slack reintentó con el mismo event_id. Confirma al instante, guarda los event_id procesados y omite repetidos; JevModel no tiene clave de idempotencia que lo haga por ti.

¿Puede el bot clasificar canales privados o mensajes directos?

Solo con los eventos de mensaje y scopes de history propios de esos tipos de conversación, y solo donde el bot sea miembro. Esta guía cubre canales públicos; para lo privado, obtén el acuerdo explícito de los responsables del workspace y envía el mínimo texto necesario.

¿Cuánto cuesta clasificar mensajes de Slack con Jev?

Cada mensaje clasificado es una petición normal a la API cobrada en tokens de tu saldo; las cuentas verificadas empiezan con 100.000 tokens gratis, y el Choice y el Noul de una misma petición se cobran juntos. Filtrar mensajes de bots, ediciones y charla muy corta antes de llamar reduce el uso. Consulta la guía de precios para los paquetes actuales.

JevModel es independiente y no está afiliado a TypeSafe AI.