Este artículo está basado en un experimento con las transcripciones de las mañaneras de Claudia Sheinbaum (repositorio de @nostrodata, CC BY-SA 4.0). Lo hice junto con varios agentes de investigación, como parte de un proceso semiautomático que estoy armando: yo decido qué se prueba y reviso, ellos escriben los scripts, corren las pruebas y me entregan un reporte técnico.

Introducción

Jev es un modelo de TypeSafe que toma decisiones tipadas. Le das un texto y preguntas con opciones, y te regresa probabilidades en lugar de texto. Hay tres tipos de pregunta: noul (la probabilidad de que algo sea verdad), choice (escoger de una lista) y score (una escala). Como no genera texto, no hay nada que parsear.

Ya lo había probado en inglés. La documentación dice que el inglés es el idioma principal y que los demás funcionan con menos precisión, así que quería ver cuánto se pierde en español con texto real, cuál de las variantes conviene y qué problemas salen al usarlo en serio.

Hoy hay tres variantes que hablan el mismo API (/v1/systemone), así que el mismo código funciona con cualquiera. TypeSafe es la original. Codiv ofrece OpenJev, un modelo con pesos abiertos (lo puedes descargar) y un API gratis compatible. Laya es una librería de Python que hace lo mismo en tu máquina con modelos tipo BERT: trae uno para inglés y uno multilingüe, y aquí usé el multilingüe.

variantemodelodónde corre
TypeSafe Jevjev-latestAPI de TypeSafe, de paga
Codiv OpenJevopenjev-latestAPI de Codiv, gratis, pesos abiertos
Layalaya-multilinguallocal, en CPU

Resumen

  • TypeSafe si puedes pagar. Es el más preciso y todo el experimento costó menos de 4 centavos de dólar.
  • Codiv si lo quieres gratis. Queda cerca, pero sus probabilidades salen bajas y hay que ajustar a partir de qué valor cuentas un sí.
  • Laya sin fine-tuning todavía no sirve para esto.
  • TypeSafe y Codiv cortan en 120 preguntas por request sin avisar.

El Corpus

Usé Conferencias Matutinas de la Presidenta Claudia Sheinbaum, un repositorio de @nostrodata con las versiones estenográficas de las mañaneras desde octubre de 2024, en CSV. La licencia es CC BY-SA 4.0 y solo piden que los menciones. También tienen las de AMLO con el mismo formato.

Viene una carpeta por día con el CSV de la conferencia completa, el mismo contenido partido por participante y unas imágenes con las palabras más usadas. Yo solo usé el CSV completo, donde cada renglón es una intervención:

Participante,Texto,Sentimiento,Palabras,Dia,Mes,Anio
TITULAR DEL SECRETARIADO EJECUTIVO DEL SISTEMA NACIONAL DE SEGURIDAD PUBLICA,"Con su permiso, Presidenta.",0.0,4,8,4,2025

La columna Sentimiento hay que ignorarla, el README avisa que todavía no es válida.

Mi copia llega al 22 de agosto de 2025: 221 conferencias, 84,547 intervenciones, unos 2.5 millones de palabras y 377 participantes. La Presidenta tiene el 45.7% de las intervenciones y la prensa el 17.8%, y la mediana es de 27 palabras por intervención.

Cada intervención es el state que recibe Jev. Descarté las de menos de 40 caracteres, que son casi puros saludos y "Con su permiso, Presidenta.", y quedó el 84%.

El Experimento

Un request lleva el texto y las preguntas:

body = {
    "model": "jev-latest",
    "state": "Hoy vamos a informar sobre el plan de Pemex para este año...",
    "questions": {
        "dep_pemex": {
            "type": "noul",
            "instructions": "¿Se menciona Pemex?",
            "criteria": {"false": "No se menciona", "true": "Sí se menciona"},
        }
    },
}

La respuesta trae una probabilidad por pregunta. Recortada, se ve así:

{"answers": {"dep_pemex": {"noul": 0.97}}}

Arriba de 0.5 lo cuento como sí.

Hice dos pruebas. La primera son 120 preguntas de tipo "¿Se menciona X?" sobre estados, dependencias, programas sociales, personas, países, fechas, cifras y palabras clave. La respuesta correcta la calcula un matcher, o sea, código que busca la palabra en el texto con una lista de variantes:

add("dependencia", "dep_pemex", "¿Se menciona Pemex?",
    {"kind": "any", "alts": ["pemex", "petroleos mexicanos"]})

Para esto bastaría una regex, y justo por eso lo escogí: con la respuesta conocida puedo revisar si las probabilidades son ciertas.

Casi todo lo que pregunté es raro en el corpus. Aguascalientes aparece en el 0.07% de las intervenciones, así que con textos al azar más del 90% de las respuestas serían "no", y un modelo que siempre diga que no sacaría buena calificación. Por eso tomé, para cada pregunta, 8 textos donde la respuesta es sí y 8 donde es no: 1,920 decisiones por variante. De aquí en adelante les digo positivos a los textos donde la respuesta correcta es sí, y negativos a los demás.

La segunda se parece más a un uso real. Son 120 preguntas de la prensa y una pregunta choice, "¿A qué área del gobierno corresponde esta pregunta?", con 10 opciones: seguridad, salud, economía, educación, bienestar, infraestructura, energía, exterior, política y otros. Aquí no hay regex que valga, y las etiquetas las puso un agente con una descripción corta de cada área (seguridad, por ejemplo, era "delincuencia, violencia, cárteles, fuerzas armadas, policía, derechos humanos"). Esas mismas descripciones iban en los criterios de la pregunta. Tiró 25 preguntas que eran fragmentos sin contexto y las cambió por otras del mismo mes.

Para comparar uso la exactitud con umbral de 0.5; el AUC, que mide qué tan bien pone los positivos arriba de los negativos sin importar el umbral (1.0 es perfecto, 0.5 es azar); y dos medidas de calibración, Brier y ECE, que dicen si cuando el modelo contesta 0.9 acierta el 90% de las veces. En esas dos, más bajo es mejor. En el ruteo también reviso la confianza, que es la probabilidad que el modelo le da a la opción que escogió.

Resultados

Menciones, 1,920 decisiones:

TypeSafeCodivLaya
exactitud97.9%93.5%71.8%
AUC0.9980.9910.822
Brier0.0170.0520.205
ECE0.0450.0880.165
latencia por request (mediana)323 ms475 ms114 ms
costo de toda la corrida$0.029gratislocal

Ruteo, 120 preguntas de prensa:

TypeSafeCodivLaya
exactitud87.5%82.5%46.7%
errores152164
confianza promedio cuando acierta0.9060.9340.666
confianza promedio cuando falla0.8200.8360.418

Política es el área más común (28 de las 120), así que contestar siempre "política" daría 23.3%; TypeSafe y Codiv están muy arriba de eso. Muchas de las confusiones son discutibles: a un cártel que Estados Unidos designó grupo terrorista el agente lo etiquetó "exterior" y los modelos dijeron "seguridad". Con un solo anotador y áreas de 2 a 4 preguntas, este número me sirve para comparar variantes y nada más.

¿Dónde Falla Cada Uno?

Cada punto de esta gráfica es una decisión. Los verdes deberían quedar a la derecha y los grises a la izquierda.

Cargando gráfica…

Probabilidad que dio cada variante, separada por la respuesta correcta. La línea punteada es 0.5. Pasa el cursor sobre un punto para ver la pregunta.

TypeSafe

De sus 38 fallos, 18 son de siglas. En 7 la pregunta decía "Semar" y el texto "Secretaría de Marina":

> "…personal de la Secretaría de Marina detuvo a 5 personas…"
typesafe  0.26
codiv     0.58
laya      0.01

> "(INICIA VIDEO DE LA SECRETARÍA DE MARINA)"
typesafe  0.23
codiv     0.66
laya      0.04

Lo mismo con Hacienda y SHCP (7) y Agricultura y SADER (4). Aquí la culpa es más de la pregunta que del modelo: los criterios solo decían "Sí se menciona", y leerlo literal es razonable.

Codiv

Codiv es el caso raro. A los negativos les da casi cero (0.005 en promedio), pero a los positivos 0.819, contra 0.936 de TypeSafe, y con el umbral en 0.5 dice que no a 123 positivos. Mueve el umbral:

Cargando gráfica…

Exactitud de cada variante según el umbral. La línea gris punteada es 0.5.

Con el umbral casi en cero sube a 98.3%, muy cerca de TypeSafe: como a los negativos les da prácticamente cero, cualquier cosa un poco arriba ya es un positivo. Pero ese umbral lo escogí viendo las respuestas correctas, así que el 98.3% es optimista. Si vas a usar Codiv con otro umbral, escógelo con un set de textos tuyo y mide con otro distinto.

Donde más me sorprendió fue en menciones literales. En 7 de los 8 textos de "¿Se menciona a Donald Trump?" el texto dice "Trump" tal cual y Codiv contesta que no; con SAT pasa lo mismo. Con las siglas tampoco es consistente: el nombre completo de la Secretaría de Marina sí lo conecta con Semar, mejor que TypeSafe, pero a los 7 textos que dicen "Secretaría de Relaciones Exteriores" les da 0.00 para SRE.

> "¿Cuál es el nuevo panorama ahora que no se va a reunir con el presidente Donald Trump?"
typesafe  0.99
codiv     0.02
laya      0.32

> "El SAT, la CRE, que próximamente se va a convertir en la Comisión Nacional de Energía…"
typesafe  0.99
codiv     0.31
laya      0.38

Con Trump no sé qué pasa. El primer ejemplo dice "Donald Trump" completo y aun así le da 0.02.

Laya

Corre local y es la más rápida, 114 ms por request en CPU, pero sin fine-tuning casi no distingue positivos de negativos: a los positivos les da 0.52 en promedio, casi una moneda al aire. Su README ya avisa que los checkpoints base están pensados para hacerles fine-tuning.

El Tope de 120 Preguntas

Jev está pensado para hacer muchas preguntas sobre el mismo texto en un solo request. Antes de la corrida probé cuántas aguanta cada variante, y TypeSafe y Codiv regresan solo las primeras 120, con HTTP 200 y sin avisar:

Mandas 256 preguntas Recibes 120 respuestas 136 sin respuesta y sin error tope: 120 HTTP 200, sin campo de aviso Con 1,024 preguntas pasa lo mismo: regresa las primeras 120.
Probado con un texto de 500 caracteres y 16, 64, 256 y 1,024 preguntas. Laya no tiene tope; lo probé hasta 320, a unos 200 ms por pregunta en CPU.

Yo mando paquetes de 96, el 80% del tope para dejar margen, y reviso que regresen todas:

def paquetes(preguntas: dict, n: int = 96):
    ids = list(preguntas)
    for i in range(0, len(ids), n):
        yield {k: preguntas[k] for k in ids[i:i + n]}


def preguntar(client, model, state, preguntas):
    respuestas = {}
    for paquete in paquetes(preguntas):
        body = {"model": model, "state": state, "questions": paquete}
        answers = client.post("/v1/systemone", json=body).json()["answers"]
        faltan = set(paquete) - set(answers)
        if faltan:
            raise RuntimeError(f"regresó {len(answers)} de {len(paquete)} preguntas")
        respuestas.update(answers)
    return respuestas

El tamaño del paquete no cambia las respuestas. Repetí 16 preguntas 5 veces y también las metí en paquetes de 64, y ninguna cambió de sí a no. Lo que sí se mueve un poco es la probabilidad: en promedio 0.005 en TypeSafe (un 0.970 puede salir 0.975 en otra corrida) y 0.0004 en Codiv. Eso no alcanza para cambiar ninguna respuesta.

Otros Detalles

Lo de Semar se arregla escribiendo mejor la pregunta: poner la sigla y el nombre completo, y decir en el criterio que cualquiera de los dos cuenta. No lo probé, pero sería algo así:

"dep_semar": {
    "type": "noul",
    "instructions": "¿Se menciona la Secretaría de Marina (Semar)?",
    "criteria": {
        "false": "No se menciona",
        "true": "Se menciona como Semar o como Secretaría de Marina",
    },
}

Mandar una pregunta por request sale caro. Como cada pregunta tenía sus propios textos, casi nunca coincidieron dos preguntas en el mismo texto: de 1,891 requests, 1,862 llevaron una sola pregunta. TypeSafe cobró unos 360 tokens por request, mucho más que lo que pesa el texto, porque también cobra su plantilla interna. En la prueba del tope, con 64 preguntas sobre el mismo texto, salió a unos 47 tokens por pregunta. No es el mismo texto, pero la diferencia es grande. Igual las 1,920 decisiones costaron 2.9 centavos de dólar, así que esto solo importa con volumen grande.

Codiv regresa 403 de Cloudflare (error 1010) si llamas con urllib y su User-Agent por defecto. Con uno de navegador funciona.

Con Laya conviene forzar el idioma. Laya escoge solo entre su modelo de inglés y el multilingüe según el texto, y en mis pruebas mandó al de inglés frases cortas en español sin acentos, como "Hola, necesitamos un reembolso por un cargo duplicado.". No medí qué tan mal contesta el de inglés con español, pero no está entrenado para eso, y su documentación dice que con otros alfabetos contesta mal y con mucha confianza. Ese modelo además avisa al cargarlo que su calibración viene mal ajustada:

from laya import Router

router = Router(device="cpu")
r = router.predict(texto, preguntas, model="multilingual", lang="es")

El idioma de las preguntas también importa, por lo menos en Laya. En una prueba rápida aparte le pasé un mensaje de cliente en español que termina con "o cancelamos el plan", y le pregunté si el cliente amenaza con cancelar:

> "Hola, nos cobraron dos veces marzo. Por favor devuelvan el cargo duplicado hoy o cancelamos el plan."
pregunta en inglés  ("Does the user threaten to cancel or leave?")   0.03
pregunta en español ("¿El usuario amenaza con cancelar o irse?")     0.86

El mensaje es el mismo y la respuesta correcta es sí; solo cambia el idioma de la pregunta. Es un solo caso y no lo probé con TypeSafe ni con Codiv, pero yo escribiría las preguntas en el idioma del texto.

Explorador

Escoge una pregunta y ve los 16 textos que se usaron, con lo que contestó cada variante. En rojo están los fallos.

Cargando datos…

Bugs en las Respuestas Correctas

En la primera corrida, varios "fallos" de los modelos eran bugs de los matchers:

preguntael matcher contaba…corrección
SAT"desatada", "satelital"palabra completa
Semar"SEMARNAT"palabra completa
algún mes"El Mayo" Zambadaexigir contexto de fecha
agua"aguantar"palabra completa
salud"saludar"palabra completa
Beca Benito Juárezel aeropuerto y la alcaldía"beca" cerca del nombre
Beca Rita Cetinala persona, no la beca"beca" cerca del nombre
plazo"28 años" (una edad)solo días, semanas o meses
Salud Casa por Casacualquier visita "casa por casa"el nombre completo del programa
cifra en millonesno contaba "millón" en singularaceptar singular y plural

Corregimos los diez y repetimos todo; los números de arriba son de la segunda corrida. Dejé algunas ambigüedades: estados que también son apellidos (Hidalgo, Guerrero, Morelos), Ciudad Hidalgo, que está en Chiapas, y "mexicana" contada como mención de México.

Conclusión

Jev sí funciona en español. TypeSafe falló 38 veces de 1,920, y casi la mitad fueron siglas que la pregunta no explicaba. Lo que más me cambió la forma de usarlo no tiene que ver con el idioma: el tope de 120 preguntas que no avisa, y que con Codiv hay que escoger otro umbral.

Si tuviera que meterlo hoy a un sistema, usaría TypeSafe y en las preguntas pondría la sigla y el nombre completo ("¿Se menciona la Secretaría de Marina (Semar)?"). Codiv lo dejaría para algo donde el costo pese más y tenga datos para escoger el umbral. A Laya le falta el fine-tuning, y eso es lo siguiente que quiero probar, con este mismo corpus.

Los números son de jev-latest y openjev-latest del 25 de septiembre de 2026. Son alias, así que pueden cambiar.