Enrutamiento de tickets con IA: modelo decisional en Python
Aprende a clasificar y enrutar incidencias con el modelo Strands Decider 2B en local, con umbrales de confianza y derivación automática a agentes humanos.

👁 113 lecturas
Un modelo decisional encaja mejor en el enrutamiento de incidencias que un modelo de chat conversacional: asigna directamente el departamento adecuado de tu lista, devuelve un índice de confianza y te permite remitir casos dudosos a un agente humano de forma automática. A continuación te mostramos un script funcional en Python con el modelo abierto Strands Decider 2B que probamos en local, junto con sus métricas y dos errores habituales que detectamos durante las pruebas. Explicamos qué son estos modelos y cómo rinden en nuestra guía sobre modelos decisionales de IA.
Las cantidades en EUR son conversiones orientativas de los precios publicados en USD, calculadas con el tipo de referencia del BCE del 2026-10-12: 1 EUR = 1.1181 USD. No son precios locales confirmados ni importes finales con impuestos. El proveedor y el banco pueden aplicar otros tipos o cargos. ECB.
Cómo funciona este flujo
El script procesa cada incidencia y devuelve cuatro datos clave:
- departamento: uno de los 6 definidos (pagos, envíos, devoluciones, cuenta, incidencias técnicas, ventas) o bien «otros»,
- confianza: la certeza de la elección, valorada entre 0 y 1,
- cola de destino: el departamento correspondiente si la certeza supera el corte, o
a_humanosi no lo alcanza, - urgencia: si la incidencia requiere respuesta inmediata durante el día.
La salida genera una línea JSON por cada consulta, ideal para integrarla en herramientas de helpdesk, hojas de cálculo o flujos de automatización con IA.
Requisitos previos
- Python 3.10 o superior.
- PyTorch: preferiblemente compilado con soporte CUDA si dispones de una tarjeta NVIDIA (el modelo consume algo menos de 4 GB de VRAM). También funciona en CPU, aunque con menor velocidad.
- La librería
strands-decider(licencia Apache-2.0). Durante el primer inicio descargará unos 4,4 GB: el modelo base Qwen3.5-2B-Base junto con el adaptador desarrollado por AWS.
pip install strands-decider
En entornos Windows, el instalador pip a veces sustituye PyTorch por una variante sin soporte CUDA. Puedes comprobarlo ejecutando python -c "import torch; print(torch.cuda.is_available())" en tu terminal; si el resultado es False, reinstala PyTorch seleccionando tu versión de CUDA desde el índice oficial de pytorch.org. Nos ocurrió exactamente esto durante las pruebas en nuestro equipo.
Script routing.py
# Enrutamiento de tickets con el modelo decisional Strands Decider 2B (en local, Apache-2.0).
# pip install strands-decider (ademas de PyTorch con CUDA si utilizas GPU NVIDIA)
import json, sys
from strands_decider.infer import load_engine
from strands_decider.schema import SystemOneRequest
MODEL = "StrandsAgents/strands-decider-2B-hobson-v19"
DZIALY = {
"platnosci": "Płatności, faktury, obciążenia karty, abonament",
"dostawa": "Wysyłka, kurier, śledzenie paczki, adres dostawy",
"zwrot": "Zwroty towaru, reklamacje, wymiana, uszkodzony produkt",
"konto": "Logowanie, hasło, dane konta, bezpieczeństwo konta, RODO",
"awaria": "Błędy aplikacji lub strony, awarie, integracje, wydajność",
"sprzedaz": "Pytania o ofertę, plany, rabaty, wdrożenia dla firm",
"inne": "Z treści nie da się ustalić, czego dotyczy zgłoszenie",
}
PROG_DZIAL = 0.60 # Por debajo de esta confianza, el caso pasa a un humano
PROG_PILNE = 0.40 # Umbral ajustado en nuestras pruebas; calibralo con tus datos
silnik = load_engine(MODEL, device="cuda") # "cpu" tambien funciona, con mayor latencia
def rozdziel(tekst: str) -> dict:
zapytanie = SystemOneRequest(state=tekst, questions={
"dzial": {"type": "choice", "instructions": "Do którego działu trafia to zgłoszenie?", "criteria": DZIALY},
"pilne": {"type": "noul", "instructions": "Czy zgłoszenie wymaga reakcji jeszcze dziś (awaria, utrata pieniędzy, włamanie, nieprzekraczalny termin)?"},
})
odp = silnik.evaluate(zapytanie).answers
dzial, pewnosc, p_pilne = odp["dzial"].choice, odp["dzial"].confidence, odp["pilne"].noul
kolejka = dzial if pewnosc >= PROG_DZIAL and dzial != "inne" else "a_humano"
return {"cola": kolejka, "departamento": dzial, "confianza": round(pewnosc, 2),
"urgente": p_pilne >= PROG_PILNE, "p_urgente": round(p_pilne, 2)}
if __name__ == "__main__":
for linia in sys.stdin:
if linia.strip():
print(json.dumps({"texto": linia.strip()[:60], **rozdziel(linia)}, ensure_ascii=False))
Traducción de las instrucciones y criterios evaluados en el código anterior:
- Instrucción de departamento: «¿A qué departamento corresponde esta consulta?». Criterios:
platnosci(«Pagos, facturas, cargos en tarjeta, suscripción»),dostawa(«Envíos, mensajería, seguimiento de paquete, dirección de entrega»),zwrot(«Devolución de producto, reclamaciones, cambios, producto dañado»),konto(«Inicio de sesión, contraseña, datos de cuenta, seguridad, RGPD»),awaria(«Errores de app o web, caídas del servicio, integraciones, rendimiento»),sprzedaz(«Preguntas sobre oferta comercial, planes, descuentos, cuentas corporativas»),inne(«Por el contenido no es posible determinar de qué trata la consulta»). - Instrucción de urgencia: «¿Requiere esta consulta atención hoy mismo (caída del servicio, pérdida económica, brecha de seguridad, plazo improrrogable)?».
Decisiones de diseño aplicadas:
- Probamos el sistema en polaco. En nuestro banco de pruebas mantuvimos las etiquetas y descripciones en polaco, comprobando que el modelo comprende descripciones en ese idioma con la misma precisión que en inglés.
- Dos consultas agrupadas en una sola petición. El modelo evalúa el texto de entrada una sola vez y procesa las preguntas adicionales casi sin coste computacional extra. Añadir una tercera variable (por ejemplo, «¿el cliente amenaza con cancelar el servicio?») apenas incrementa la latencia.
- La lógica de decisión reside en el código, no en el modelo. El modelo devuelve exclusivamente probabilidades normalizadas; los umbrales de decisión y el enrutamiento a colas se gestionan desde Python, lo que permite reajustar reglas sin volver a entrenar nada.
Puesta en marcha y resultados
El script lee incidencias directamente desde la entrada estándar, una por línea:
python routing.py < incidencias.txt
Estos fueron los resultados obtenidos en nuestra tarjeta RTX 4090 con seis mensajes reales que el modelo no había visto previamente en el entrenamiento:
| Incidencia | Sin opción «otros» | Con opción «otros» |
|---|---|---|
| Od rana nie działa logowanie przez Google, cały zespół stoi. («Desde la mañana no funciona el login con Google, todo el equipo está parado») | cuenta 0,54 - a humano, urgente | cuenta 0,46 - a humano, urgente |
| Proszę o fakturę VAT na firmę do zamówienia 77120. («Solicito factura con IVA para empresa del pedido 77120») | pagos 0,46 - a humano | otros 0,63 - a humano |
| Paczka wróciła do nadawcy, bo kurier nie mógł znaleźć adresu. («El paquete ha vuelto al remitente porque el transportista no encontró la dirección») | envíos 0,96 | envíos 0,90 |
| Czy macie zniżki dla szkół i uczelni? («¿Tenéis descuentos para colegios y universidades?») | ventas 0,58 - a humano | ventas 0,31 - a humano |
| Hej, mam pytanie. Coś mi nie gra. («Hola, tengo una pregunta. Algo no me cuadra») | incidencia técnica 0,80 | incidencia técnica 0,41 - a humano |
| Zwracam kurtkę, ale nie wiem, czy zwrot pójdzie na kartę czy na konto. («Devuelvo una chaqueta, pero no sé si el reembolso irá a la tarjeta o a la cuenta») | devolución 0,94 | devolución 0,84 |
Cada inferencia tardó aproximadamente 135 ms en la tarjeta gráfica, tras un tiempo inicial de carga del modelo de 16 segundos. Todas las consultas que el modelo enrutó de forma automática fueron asignadas a su departamento correcto. Además, solo marcó como urgente el problema crítico de acceso que bloqueaba el trabajo del equipo.
Trampa 1: exceso de confianza ante mensajes vagos
En nuestra primera iteración no contemplamos la categoría «otros». Ante un mensaje vacío de contexto como Hej, mam pytanie. Coś mi nie gra. («Hola, tengo una pregunta. Algo no me cuadra»), el modelo lo asignó a fallos técnicos con una confianza de 0,80. Como superaba el umbral de corte, habría acabado en la bandeja del equipo de soporte técnico sin motivo. No se trata de un fallo del modelo: al forzarle a elegir entre seis categorías cerradas, las probabilidades deben sumar obligatoriamente 1. Un modelo decisional no responderá «no lo sé» a menos que le proporciones explícitamente esa alternativa.
La solución consiste en definir la opción inne (otros) con una descripción clara: «Por el contenido no es posible determinar de qué trata la consulta», acompañada de una instrucción en el script para derivar siempre esa categoría al equipo humano. Tras este ajuste, la confianza del mensaje vago cayó a 0,41 y fue desviado correctamente a revisión manual.
Trampa 2: la opción «otros» reduce la confianza general
Añadir una categoría cajón de sastre tiene un precio: la certeza también disminuye en consultas claras (el paquete bajó de 0,96 a 0,90; la devolución de la prenda, de 0,94 a 0,84), y la solicitud de factura con IVA fue clasificada erróneamente en «otros» en lugar de pagos. Para mitigar este efecto:
- Añade al criterio el vocabulario real de tus usuarios. Si la petición de factura con IVA acaba en «otros», amplía la descripción de pagos incluyendo términos específicos como «factura con IVA, rectificativa, desglose de impuestos».
- Determina los umbrales con tu propio historial. Reúne entre 50 y 100 incidencias clasificadas manualmente, procésalas con el script y evalúa a partir de qué umbral de corte el sistema no comete falsos positivos. Nuestro corte de 0,60 sirve como punto de partida, no como regla universal.
- Monitoriza el porcentaje de derivación humana. Si supera el 30-40% del volumen total, suele indicar que las descripciones de las áreas son excesivamente genéricas o que sus competencias se solapan.
Ajuste del umbral de urgencia
En nuestra batería de 30 consultas reales probadas en polaco, Strands Decider asignó a los casos urgentes valores de probabilidad situados entre 0,22 y 0,47. Si hubiéramos mantenido el umbral teórico estándar de 0,5, ningún ticket se habría marcado como prioritario; de ahí que fijáramos el corte en 0,40 en el código. En cambio, el modelo de mayor escala Clef-flash de Cloudflare mostró el sesgo opuesto y rindió de forma óptima situando el umbral en 0,95. La conclusión es clara: trata los valores numéricos del modelo como un orden de clasificación relativo y calibra la frontera con tus propios datos.
Cómo migrar a otros modelos
Strands Decider, Cloudflare Clef, junto con Kev y Jev de TypeSafe, comparten la misma estructura de consultas (formato System One: state + questions). Adaptar el script a cualquiera de ellos requiere únicamente cambiar el conector de inferencia:
- Clef-flash en local (tarjeta de 24 GB de VRAM): utiliza la función
systemone()incluida enjoint_schema_model.pydentro del repositorioCloudflare/clef-flash. En nuestro entorno demostró ser algo más veloz (112 ms) y consistente, aunque exige 18 GB de memoria de vídeo dedicada. Analizamos su lanzamiento en nuestro artículo sobre Cloudflare Clef. - Workers AI de Cloudflare: petición
POSTcontra/ai/run/@cf/cloudflare/clef-flashcon la misma estructura JSON, a un coste de 0,0805 EUR (0,09 USD) por millón de tokens de entrada con cuota diaria gratuita. Requiere cuenta en Cloudflare. - Servidor HTTP de Strands: ejecutando
strands-decider serve ... --port 8000y realizando llamadas a/v1/systemone, lo que evita que cada script deba cargar los pesos en memoria de forma independiente.
Puedes profundizar en las especificaciones, licencias y rendimiento de este modelo en nuestro análisis de Strands Decider 2B.
Cuándo no conviene usar un modelo decisional
- Si necesitas redactar directamente una contestación para el usuario; en ese caso precisas un LLM generativo, aunque el modelo decisional puede seleccionar previamente la plantilla adecuada.
- Si gestionas un volumen muy bajo de incidencias diarias; resulta más directo pedirle un JSON a un modelo comercial estándar, tal como explicamos en nuestra guía sobre salidas estructuradas con IA.
- Si la estructura organizativa de tus departamentos cambia semanalmente y careces de datos históricos etiquetados, pues será imposible fijar umbrales fiables.
Fuentes
- Strands Decider 2B en Hugging Face
- Repositorio strands-labs/strands-decider en GitHub
- Script evaluado el 5 de octubre de 2026 en un equipo con RTX 4090, Windows 11, Python 3.12, PyTorch 2.11, transformers 5.18 y strands-decider 0.1.0.
More on this from FULGOR
herramientas de ia·¿Qué IA elegir para música de vídeo? Eleven Music, Stable o SunoPara música de fondo en vídeo, Eleven Music destaca por control y licencia desde 6 USD. Stable Audio 3 y Suno completan las mejores alternativas.

