# Instalar el SDK (si no está instalado)
!pip install typesafe-sdk
Collecting typesafe-sdk Downloading typesafe_sdk-0.7.1-py3-none-any.whl.metadata (2.6 kB) Requirement already satisfied: httpx2>=2.0.0 in /usr/local/lib/python3.13/dist-packages (from typesafe-sdk) (2.12.0) Requirement already satisfied: pydantic>=2.12.0 in /usr/local/lib/python3.13/dist-packages (from typesafe-sdk) (2.13.5) Requirement already satisfied: pydantic-core>=2.41.1 in /usr/local/lib/python3.13/dist-packages (from typesafe-sdk) (2.46.5) Requirement already satisfied: tenacity>=9.0.0 in /usr/local/lib/python3.13/dist-packages (from typesafe-sdk) (9.1.4) Requirement already satisfied: typing-extensions>=4.13.0 in /usr/local/lib/python3.13/dist-packages (from typesafe-sdk) (4.16.0) Requirement already satisfied: anyio>=4.10 in /usr/local/lib/python3.13/dist-packages (from httpx2>=2.0.0->typesafe-sdk) (4.14.2) Requirement already satisfied: httpcore2==2.12.0 in /usr/local/lib/python3.13/dist-packages (from httpx2>=2.0.0->typesafe-sdk) (2.12.0) Requirement already satisfied: idna>=3.18 in /usr/local/lib/python3.13/dist-packages (from httpx2>=2.0.0->typesafe-sdk) (3.19) Requirement already satisfied: truststore>=0.10 in /usr/local/lib/python3.13/dist-packages (from httpx2>=2.0.0->typesafe-sdk) (0.10.4) Requirement already satisfied: h11>=0.16 in /usr/local/lib/python3.13/dist-packages (from httpcore2==2.12.0->httpx2>=2.0.0->typesafe-sdk) (0.16.0) Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.13/dist-packages (from pydantic>=2.12.0->typesafe-sdk) (0.8.0) Requirement already satisfied: typing-inspection>=0.4.2 in /usr/local/lib/python3.13/dist-packages (from pydantic>=2.12.0->typesafe-sdk) (0.4.4) Downloading typesafe_sdk-0.7.1-py3-none-any.whl (36 kB) Installing collected packages: typesafe-sdk Successfully installed typesafe-sdk-0.7.1
# Importaciones principales
from typesafe_sdk import (
TypeSafeClient,
AsyncTypeSafeClient,
Noul,
Choice,
Score,
Question,
RetryPolicy,
TypeSafeError,
TypeSafeAuthenticationError,
TypeSafeAPIError,
)
import os
import asyncio
print("✅ Importaciones completadas exitosamente")
✅ Importaciones completadas exitosamente
Configurar el cliente¶
TypeSafeClient es el cliente síncrono. Si la variable de entorno ya está definida, no hace falta pasar la clave a mano; aquí la leemos de forma explícita para que el notebook sea fácil de seguir.
En producción no hardcodees la API key: usa el secreto de Colab o la variable de entorno.
# Agrega tu API Key como secreto en Google Colab para que no quede expuesta
from google.colab import userdata
import os
os.environ["TYPESAFE_API_KEY"] = userdata.get("TYPESAFE_API_KEY")
# La clave ya está en el entorno (secreto de Colab)
API_KEY = os.getenv("TYPESAFE_API_KEY")
client = TypeSafeClient(api_key=API_KEY)
print("✅ Cliente TypeSafe creado exitosamente")
✅ Cliente TypeSafe creado exitosamente
Noul: probabilidad de un sí o un no¶
Un Noul responde una proposición que se puede contestar con sí o con no. El valor que recibes (noul) es un float entre 0 y 1: la probabilidad de que la respuesta sea sí.
Eso importa más de lo que parece.
0.05no significa “un poco urgente”. Significa “casi seguro que no”.0.5no es un nivel medio de intensidad. Es empate entre sí y no.0.94significa “casi seguro que sí”.
Si lo que quieres medir es una escala (poco / algo / mucho), el primitivo correcto es Score. Noul encaja cuando tu código va a aplicar un umbral:
si noul > 0.9 → escalar a humano
si noul < 0.2 → dejarlo en el bot
el resto → revisión
Casos de uso naturales: ¿pide reembolso?, ¿hay datos personales?, ¿el mensaje expresa urgencia?, ¿hay amenaza de cancelación?
En los ejemplos usamos Noul para urgencia y riesgo de churn porque el sistema posterior va a cortar con un umbral. La instrucción se formula como proposición de sí o no (“¿exige atención inmediata?”), no como una escala de intensidad.
# Ejemplo: Analizar urgencia en tickets de soporte
ticket_urgente = """
¡NECESITO AYUDA INMEDIATA! Mi sistema está caído desde hace 3 horas
y estamos perdiendo miles de dólares por minuto. Esto es inaceptable.
Por favor escalen esto AHORA MISMO.
"""
ticket_normal = """
Hola, tengo una pregunta sobre cómo usar la función de exportación.
Cuando pueda, me gustaría recibir información al respecto.
Gracias.
"""
# Definir pregunta tipo Noul
pregunta_urgencia = Noul(
instructions="¿Este ticket exige atención inmediata?"
)
print("📋 Pregunta Noul definida:")
print(f" Instrucciones: {pregunta_urgencia.instructions}")
print(f" Tipo: {type(pregunta_urgencia).__name__}")
📋 Pregunta Noul definida: Instrucciones: ¿Este ticket exige atención inmediata? Tipo: Noul
with TypeSafeClient(api_key=API_KEY) as client:
resultado = client.system_one(
state=ticket_urgente,
questions={"urgencia": pregunta_urgencia}
)
print(f"Nivel de urgencia: {resultado.nouls['urgencia'].noul:.2%}")
# Esperado: ~0.95 (95% urgente)
print("💡 Ejemplo de uso de Noul mostrado arriba")
Nivel de urgencia: 95.00% 💡 Ejemplo de uso de Noul mostrado arriba Descomenta el código para ejecutar con tu API key
Choice: una categoría entre varias¶
Choice elige una opción de un conjunto cerrado que tú defines. Además de la etiqueta ganadora (choice), la API devuelve la distribución completa (probabilities) y un confidence derivado de esa distribución.
Úsalo cuando las opciones no están ordenadas: facturación no es “más” que técnico; son colas distintas.
La calidad del resultado depende de los criterios. Una clave con None le dice al modelo “usa el nombre de la opción”. Una frase corta le dice qué entra y qué no. Si dos categorías se pisan, el confidence baja: esa es una señal útil, no un fallo del SDK.
Casos de uso: tema del ticket, tono, intención (consulta / queja / elogio), idioma, equipo responsable.
# Ejemplo: Clasificar tono de comentarios de clientes
comentarios = [
"¡Excelente servicio! Muy satisfecho con la atención.",
"El producto llegó dañado y nadie me ayuda. Pésimo servicio.",
"Recibí el pedido, todo bien."
]
# Definir pregunta tipo Choice con criterios
pregunta_tono = Choice(
instructions="¿Cuál es el tono emocional de este comentario?",
criteria={
"positivo": "El cliente expresa satisfacción, alegría o gratitud",
"negativo": "El cliente expresa enojo, frustración o decepción",
"neutral": "El cliente informa hechos sin emoción aparente"
}
)
print("📋 Pregunta Choice definida:")
print(f" Instrucciones: {pregunta_tono.instructions}")
print(f" Criterios: {list(pregunta_tono.criteria.keys())}")
📋 Pregunta Choice definida: Instrucciones: ¿Cuál es el tono emocional de este comentario? Criterios: ['positivo', 'negativo', 'neutral']
with TypeSafeClient(api_key=API_KEY) as client:
for i, comentario in enumerate(comentarios, 1):
resultado = client.system_one(
state=comentario,
questions={"tono": pregunta_tono}
)
tono_detectado = resultado.choices["tono"].choice
print(f"Comentario {i}: '{comentario[:50]}...'")
print(f" → Tono detectado: {tono_detectado}\n")
print("💡 Ejemplo de uso de Choice mostrado arriba")
Comentario 1: '¡Excelente servicio! Muy satisfecho con la atenció...' → Tono detectado: positivo Comentario 2: 'El producto llegó dañado y nadie me ayuda. Pésimo...' → Tono detectado: negativo Comentario 3: 'Recibí el pedido, todo bien....' → Tono detectado: positivo 💡 Ejemplo de uso de Choice mostrado arriba
Score: una posición en una escala que tú defines¶
Score no es “un número del 1 al 10 mágico”. Es una rúbrica ordenada. Tú listas los niveles (de menor a mayor, o del más leve al más grave) y el modelo sitúa el estado en esa escala. El score puede caer entre dos niveles: no estás obligado a recibir un entero limpio.
Eso lo distingue de Choice. “Técnico / facturación / ventas” no tiene orden. “Cosmético → hay workaround → bloquea producción” sí.
Casos de uso: severidad de un bug, calidad de un lead, nivel de frustración, madurez de un requisito.
En el ejemplo de abajo hay cuatro niveles (índices 0 a 3). El score que verás es una posición sobre esa rúbrica —puede ser 2.99, no “un 3 de 10”—.
# Ejemplo: Evaluar calidad de leads de ventas
lead_alta_calidad = """
Soy el director de TI de una empresa Fortune 500.
Estamos buscando implementar su solución enterprise antes de Q4.
Tenemos presupuesto aprobado de $500k y necesitamos una demo esta semana.
Por favor contacten a mi equipo para coordinar.
"""
lead_baja_calidad = """
Hola, soy estudiante y estoy investigando para mi tesis.
¿Podrían enviarme información sobre sus productos?
Quizás en el futuro cuando tenga empresa los considere.
"""
# Definir pregunta tipo Score
pregunta_calidad = Score(
instructions="Evalúa la calidad de este lead de ventas",
criteria=[
"1-3: Lead poco calificado: sin presupuesto, sin timeline, sin autoridad",
"4-6: Lead medio: algún interés pero falta información clave",
"7-9: Lead bueno: tiene presupuesto y timeline definidos",
"10: Lead excelente: presupuesto alto, timeline urgente, decision maker"
]
)
print("📋 Pregunta Score definida:")
print(f" Criterios: {len(pregunta_calidad.criteria)} niveles")
print(f" Instrucciones: {pregunta_calidad.instructions}")
📋 Pregunta Score definida: Criterios: 4 niveles Instrucciones: Evalúa la calidad de este lead de ventas
with TypeSafeClient(api_key=API_KEY) as client:
for nombre, lead in [("Alta", lead_alta_calidad), ("Baja", lead_baja_calidad)]:
resultado = client.system_one(
state=lead,
questions={"calidad": pregunta_calidad}
)
puntaje = resultado.scores["calidad"].score
print(f"Lead {nombre}: score = {puntaje:.2f} (escala 0–3)")
Lead Alta: score = 2.99 (escala 0–3) Lead Baja: score = 0.01 (escala 0–3)
Varias preguntas en una sola llamada¶
El valor aparece cuando dejas de hacer “una pregunta bonita” y pasas a un paquete de juicios que tu código puede componer.
Un correo de un cliente enterprise enfadado no es una sola etiqueta. Es, al mismo tiempo:
- una proposición de urgencia (Noul)
- un tono (Choice)
- un segmento de valor (Score)
- una proposición de riesgo de cancelación (Noul)
Una llamada. Cuatro respuestas tipadas. A partir de ahí, reglas tuyas: si el riesgo de churn supera 0.7 y el segmento es alto, no lo dejas en la cola general.
En el bloque siguiente el Score de segmento usa una lista de niveles —la forma que espera el SDK—, no un diccionario de rangos en dólares. El modelo no extrae el contrato anual como contable; coloca el caso en la rúbrica que le diste.
# Ejemplo: Análisis completo de un email de cliente
email_cliente = """
Estimado equipo de soporte,
Llevo 3 días intentando acceder a mi cuenta y nadie me responde.
Esto es absolutamente inaceptable para una empresa como la vuestra.
Si no resuelven esto hoy, voy a cancelar mi suscripción enterprise
de $10,000 anuales y me cambiaré a la competencia.
Espero una respuesta INMEDIATA.
Carlos Rodríguez
Director de Operaciones, TechCorp Inc.
"""
# Definir múltiples preguntas
analisis_completo = {
"urgencia": Noul(
instructions="¿Este caso exige atención inmediata?"
),
"tono": Choice(
instructions="¿Cuál es el tono del cliente?",
criteria={
"calmado": "Cliente tranquilo y razonable",
"frustrado": "Cliente molesto pero constructivo",
"enojado": "Cliente furioso y amenazante"
}
),
"segmento_cliente": Score(
instructions="¿En qué segmento de valor encaja este cliente?",
criteria=[
"Cliente gratuito o trial",
"Cliente SMB",
"Cliente mid-market",
"Cliente enterprise",
]
),
"riesgo_churn": Noul(
instructions="¿Hay una amenaza clara de que este cliente cancele?"
)
}
print("📋 Análisis múltiple definido:")
for nombre, pregunta in analisis_completo.items():
tipo = type(pregunta).__name__
print(f" • {nombre}: {tipo}")
📋 Análisis múltiple definido: • urgencia: Noul • tono: Choice • segmento_cliente: Score • riesgo_churn: Noul
with TypeSafeClient(api_key=API_KEY) as client:
resultado = client.system_one(
state=email_cliente,
questions=analisis_completo
)
print("\n📊 RESULTADOS DEL ANÁLISIS:")
print(f" Urgencia: {resultado.nouls['urgencia'].noul:.2%}")
print(f" Tono: {resultado.choices['tono'].choice}")
print(f" Segmento (score): {resultado.scores['segmento_cliente'].score}")
print(f" Riesgo de churn: {resultado.nouls['riesgo_churn'].noul:.2%}")
# Recomendación automática
if resultado.nouls['riesgo_churn'].noul > 0.7:
print("\n⚠️ RECOMENDACIÓN: Escalar inmediatamente a gerente de cuenta")
📊 RESULTADOS DEL ANÁLISIS: Urgencia: 87.00% Tono: enojado Segmento (score): 2.97 Riesgo de churn: 55.00%
Cliente asíncrono¶
Cuando clasificas muchos textos (un lote de tickets al abrir el turno, un stream de comentarios), el cliente síncrono se queda corto: esperas una respuesta para lanzar la siguiente.
AsyncTypeSafeClient permite disparar varias llamadas con asyncio.gather. El contrato de las preguntas no cambia. Cambia el modo en que esperas.
Esto encaja de forma natural si más adelante envuelves TypeSafe detrás de FastAPI: el endpoint puede ser async y no bloquear el event loop mientras System One responde.
async def ejemplo_asincrono():
"""Ejemplo de uso asíncrono del cliente TypeSafe"""
async with AsyncTypeSafeClient(api_key=API_KEY) as client:
# Preparar múltiples análisis en paralelo
textos = [
"Excelente producto, muy recomendado",
"Terrible experiencia, no lo compren",
"Producto promedio, cumple su función"
]
pregunta_sentimiento = Choice(
instructions="Clasifica el sentimiento",
criteria={"positivo": None, "negativo": None, "neutral": None}
)
# Ejecutar análisis en paralelo
tareas = [
client.system_one(state=texto, questions={"sentimiento": pregunta_sentimiento})
for texto in textos
]
resultados = await asyncio.gather(*tareas)
for i, (texto, resultado) in enumerate(zip(textos, resultados), 1):
sentimiento = resultado.choices["sentimiento"].choice
print(f"{i}. '{texto}' → {sentimiento}")
await ejemplo_asincrono()
1. 'Excelente producto, muy recomendado' → positivo 2. 'Terrible experiencia, no lo compren' → negativo 3. 'Producto promedio, cumple su función' → neutral
Reintentos y timeouts¶
Una API de modelos falla. Se satura. Tarda. Si tu clasificador de tickets muere en el primer 429, no tienes un sistema: tienes una demo.
En typesafe-sdk 0.7.1, RetryPolicy expone el número máximo de reintentos. El backoff lo resuelve el cliente. Combínalo con un timeout explícito en TypeSafeClient.
No es la parte “inteligente” del tutorial. Es la parte que hace que el resto sobreviva en producción.
retry_policy = RetryPolicy(max_retries=3)
client_con_retry = TypeSafeClient(
api_key=API_KEY,
retry=retry_policy,
timeout=30.0,
)
print("✅ Cliente con política de retry configurado")
print(f" Máximos retries: {retry_policy.max_retries}")
✅ Cliente con política de retry configurado Máximos retries: 3
Listar modelos disponibles¶
TypeSafe versiona los modelos de System One (en la documentación aparecen identificadores de la familia jev). Antes de fijar un model en código de producción, conviene listar lo que tu cuenta tiene disponible.
with TypeSafeClient(api_key=API_KEY) as client:
modelos = client.models.list()
print("📦 Modelos disponibles:")
for modelo in modelos.models:
nombre = getattr(modelo, "name", None) or getattr(modelo, "id", "desconocido")
descripcion = getattr(modelo, "description", "") or ""
print(f" • {nombre}")
if descripcion:
print(f" {descripcion}")
📦 Modelos disponibles:
• jev-latest
The latest iteration of TypeSafe's System One Model: Jev
• jev-preview
A preview version of `jev-latest`: should be better in most ways
Errores tipados¶
El SDK no te devuelve un Exception genérico y te deja adivinar. Hay tipos distintos para autenticación, rate limit, timeout, conexión, 404, 400 y el error base de API.
Tratarlos por separado no es cosmética: un 401 no se reintenta igual que un 429, y un timeout no se le enseña al usuario como “el modelo se equivocó”.
from typesafe_sdk import (
TypeSafeAuthenticationError,
TypeSafeAPIError,
TypeSafeAPIConnectionError,
TypeSafeAPITimeoutError,
TypeSafeRateLimitError,
TypeSafeNotFoundError,
TypeSafeBadRequestError,
)
def analisis_con_manejo_de_errores(texto: str, api_key: str):
"""Ejemplo robusto de manejo de errores"""
try:
with TypeSafeClient(api_key=api_key) as client:
resultado = client.system_one(
state=texto,
questions={
"sentimiento": Choice(
instructions="Sentimiento",
criteria={"positivo": None, "negativo": None, "neutral": None}
)
}
)
return resultado.choices["sentimiento"].choice
except TypeSafeAuthenticationError:
print("❌ Error: API key inválida o expirada")
raise
except TypeSafeRateLimitError as e:
print(f"⏳ Rate limit excedido. Reintentar en {e.retry_after} segundos")
raise
except TypeSafeAPITimeoutError:
print("⏱️ Timeout: La API no respondió a tiempo")
raise
except TypeSafeAPIConnectionError:
print("🔌 Error de conexión: Verifica tu conexión a internet")
raise
except TypeSafeNotFoundError:
print("🔍 Error: Recurso no encontrado (modelo inválido?)")
raise
except TypeSafeBadRequestError as e:
print(f"📝 Error en la solicitud: {e.message}")
raise
except TypeSafeAPIError as e:
print(f"💥 Error general de API: {e.status_code} - {e.message}")
raise
except TypeSafeError as e:
print(f"⚠️ Error desconocido: {e}")
raise
print("✅ Función con manejo robusto de errores definida")
print(" Tipos de errores manejados:")
print(" • AuthenticationError")
print(" • RateLimitError")
print(" • APITimeoutError")
print(" • APIConnectionError")
print(" • NotFoundError")
print(" • BadRequestError")
print(" • APIError")
print(" • TypeSafeError (base)")
✅ Función con manejo robusto de errores definida Tipos de errores manejados: • AuthenticationError • RateLimitError • APITimeoutError • APIConnectionError • NotFoundError • BadRequestError • APIError • TypeSafeError (base)
Caso de uso: clasificar tickets y decidir qué hacer¶
Hasta aquí cada primitivo por separado. Ahora el patrón que suele vivir en un backend real:
- Entra el texto del ticket.
- Se evalúa categoría, prioridad, urgencia, sentimiento y probabilidad de escalamiento.
- Tu código —no el modelo— decide la acción: guardia, nivel 2, ventas o cola estándar.
Fíjate en la división de trabajo. TypeSafe juzga el texto. Las reglas de negocio (si prioridad == critica, si prob_escalamiento > 0.7) siguen siendo tuyas. Eso es deliberado: el modelo no debería “saber” cómo opera tu empresa; debería darte señales estables para que tu sistema decida.
# Definir esquema de análisis para tickets
def analizar_ticket(ticket_texto: str, api_key: str):
"""Analiza un ticket de soporte y devuelve clasificación completa"""
preguntas_ticket = {
"categoria": Choice(
instructions="¿Cuál es la categoría principal del ticket?",
criteria={
"tecnico": "Problemas técnicos, bugs, errores de software",
"facturacion": "Pagos, facturas, reembolsos, cargos",
"ventas": "Consultas pre-venta, demos, precios",
"cuenta": "Acceso, contraseñas, configuración de cuenta",
"general": "Otras consultas generales"
}
),
"prioridad": Choice(
instructions="¿Qué prioridad debería tener este ticket?",
criteria={
"critica": "Sistema caído, pérdida de datos, múltiples usuarios afectados",
"alta": "Funcionalidad importante rota, workaround difícil",
"media": "Problema molesto pero con workaround",
"baja": "Mejoras, preguntas, cosméticos"
}
),
"urgencia_num": Noul(
instructions="¿Este ticket exige atención inmediata?"
),
"sentimiento": Choice(
instructions="Estado emocional del cliente",
criteria={
"muy_enojado": "Furioso, amenazando con irse",
"molesto": "Frustrado pero razonable",
"neutral": "Sin emoción aparente",
"satisfecho": "Contento o agradecido"
}
),
"requiere_escalamiento": Noul(
instructions="¿Este ticket requiere escalamiento a soporte nivel 2 o 3?"
)
}
with TypeSafeClient(api_key=api_key) as client:
resultado = client.system_one(
state=ticket_texto,
questions=preguntas_ticket
)
return {
"categoria": resultado.choices["categoria"].choice,
"prioridad": resultado.choices["prioridad"].choice,
"urgencia": resultado.nouls["urgencia_num"].noul,
"sentimiento": resultado.choices["sentimiento"].choice,
"prob_escalamiento": resultado.nouls["requiere_escalamiento"].noul
}
# Ejemplos de tickets para probar
tickets_ejemplo = [
# Ticket 1: Crítico técnico
"""URGENTE: Nuestra plataforma está completamente caída desde hace 2 horas.
Todos nuestros 500 usuarios no pueden acceder. Estamos perdiendo $10k por hora.
Necesitamos ayuda INMEDIATA o cancelaremos el contrato.""",
# Ticket 2: Consulta de facturación
"""Hola, tengo una pregunta sobre mi última factura. Veo un cargo de $99
que no reconozco. ¿Podrían explicarme qué es? Gracias.""",
# Ticket 3: Bug menor
"""Encontré un pequeño bug: cuando hago clic en el botón de exportar
dos veces rápido, se descarga el archivo dos veces. No es grave pero
sería bueno arreglarlo.""",
# Ticket 4: Lead de ventas
"""Somos una empresa de 200 empleados y estamos evaluando su producto
para implementarlo en Q2. ¿Podrían agendar una demo con nuestro equipo
técnico? Tenemos presupuesto aprobado."""
]
print(f"📋 {len(tickets_ejemplo)} tickets de ejemplo definidos")
📋 4 tickets de ejemplo definidos
# Analizar todos los tickets
resultados = []
for i, ticket in enumerate(tickets_ejemplo, 1):
print(f"\n🎫 TICKET {i}:")
print(f" Texto: {ticket[:80]}...")
resultado = analizar_ticket(ticket, api_key=API_KEY)
resultados.append(resultado)
print(f" Categoría: {resultado['categoria']}")
print(f" Prioridad: {resultado['prioridad']}")
print(f" Urgencia: {resultado['urgencia']:.2%}")
print(f" Sentimiento: {resultado['sentimiento']}")
print(f" Prob. escalamiento: {resultado['prob_escalamiento']:.2%}")
# Reglas automáticas de routing
if resultado['prioridad'] == 'critica':
print(" ⚠️ ACCIÓN: Escalar a equipo de guardia inmediatamente")
elif resultado['prob_escalamiento'] > 0.7:
print(" ⚠️ ACCIÓN: Asignar a soporte nivel 2")
elif resultado['categoria'] == 'ventas':
print(" 💼 ACCIÓN: Transferir a equipo de ventas")
else:
print(" ✅ ACCIÓN: Asignar a cola estándar")
🎫 TICKET 1:
Texto: URGENTE: Nuestra plataforma está completamente caída desde hace 2 horas.
Tod...
Categoría: tecnico
Prioridad: critica
Urgencia: 94.00%
Sentimiento: muy_enojado
Prob. escalamiento: 93.00%
⚠️ ACCIÓN: Escalar a equipo de guardia inmediatamente
🎫 TICKET 2:
Texto: Hola, tengo una pregunta sobre mi última factura. Veo un cargo de $99
que no...
Categoría: facturacion
Prioridad: baja
Urgencia: 29.00%
Sentimiento: neutral
Prob. escalamiento: 31.00%
✅ ACCIÓN: Asignar a cola estándar
🎫 TICKET 3:
Texto: Encontré un pequeño bug: cuando hago clic en el botón de exportar
dos veces ...
Categoría: tecnico
Prioridad: media
Urgencia: 19.00%
Sentimiento: neutral
Prob. escalamiento: 13.00%
✅ ACCIÓN: Asignar a cola estándar
🎫 TICKET 4:
Texto: Somos una empresa de 200 empleados y estamos evaluando su producto
para impl...
Categoría: ventas
Prioridad: baja
Urgencia: 48.00%
Sentimiento: neutral
Prob. escalamiento: 53.00%
💼 ACCIÓN: Transferir a equipo de ventas
Cómo elegir el tipo (y cómo no mezclarlos)¶
| Tipo | Pregunta que responde | Qué recibes | Úsalo cuando |
|---|---|---|---|
| Noul | ¿Sí o no? | noul ∈ [0, 1] = P(sí) |
Vas a aplicar un umbral |
| Choice | ¿Cuál de estas opciones? | choice, probabilities, confidence |
Las opciones no tienen orden |
| Score | ¿En qué peldaño de esta escala? | score sobre tus niveles, más distribución |
Los niveles están ordenados |
Tres reglas que evitan el error más común:
- Noul no es una escala de intensidad. Si 0.5 “debería” significar “urgencia media”, estás usando el primitivo equivocado.
- Choice no es un ranking. Si “alta / media / baja” tiene orden, es un Score (o tres Noul, si cada umbral es una decisión distinta).
- Un juicio, una pregunta. “¿Es un buen lead enterprise urgente?” mezcla segmento, calidad y tiempo. Sepáralas y combina en código.
Otras prácticas que sí importan:
- Instrucciones concretas. “Sentimiento” es flojo; “estado emocional del cliente en este mensaje” es mejor.
- Criterios que marcan frontera, no sinónimos.
- Cliente asíncrono para lotes.
try/exceptpor tipo de error, no unexcept Exceptionque lo traga todo.- Reintentos con tope. Sin tope, un outage se convierte en un bucle.