En 1865, el economista William Stanley Jevons observó algo que contradecía toda intuición: cuando la máquina de vapor de Watt hizo que quemar carbón fuera mucho más eficiente, Inglaterra no consumió menos carbón, sino muchísimo más. Cada mejora en eficiencia abarataba el uso, y cada abaratamiento abría usos nuevos que antes nadie se planteaba. Hoy lo conocemos como la paradoja de Jevons, y me parece la mejor forma de explicar lo que nos está pasando con los LLMs. En apenas tres años, el coste de que una máquina lea un párrafo y lo entienda ha caído varios órdenes de magnitud, y hemos hecho exactamente lo que Jevons predecía: hemos empezado a llamar a un modelo de lenguaje en sitios donde antes ni se nos habría ocurrido.
Y ahí está el problema, porque no todos esos sitios necesitan un modelo de lenguaje. Necesitan un juicio. Pensemos en algunas preguntas que le hacemos a un LLM desde un backend: ¿este mensaje es spam?, ¿a qué departamento va este correo?, ¿cómo de enfadado está este usuario del uno al cinco? Ninguna pide un texto como respuesta; piden un booleano, una opción de una lista cerrada o un número en una escala. Y sin embargo se las hacemos a un modelo entrenado para escribir, que nos devuelve lo único que sabe devolver: una cadena de texto. Un JSON, con suerte. Y a partir de ahí toca escribir código de parseo y validación para un espacio de respuestas que tiene cuatro miembros.
Esto es, literalmente, matar moscas a cañonazos. El cañón funciona, la mosca muere, pero el gasto es desproporcionado y el estruendo tiene consecuencias. Pagamos tokens de entrada, tokens de salida y —en los modelos actuales— tokens de razonamiento que el modelo consume antes de escribir la única palabra que le pedimos. Y pagamos algo más caro que el dinero: latencia. Una llamada que tarda varios segundos no puede vivir dentro de una petición HTTP mientras alguien espera una pantalla de confirmación, así que la mandamos a una cola, le ponemos un worker, una política de reintentos y una tabla donde apuntar si ya se procesó o no. Hemos convertido lo que debería ser un if en una pieza de infraestructura, y todo para decidir entre cuatro opciones.
Me atrevería a decir que es la forma más común de uso desmedido de recursos que tenemos ahora mismo en la industria, precisamente porque no se ve. Nadie mete un LLM en un formulario por derroche; lo mete porque es la herramienta que tiene a mano y porque funciona. La pregunta que casi nunca nos hacemos es si existe una herramienta con la forma correcta para el trabajo: una que entienda lenguaje natural igual de bien, pero que devuelva decisiones en lugar de prosa, en milisegundos y a un precio que deje de ser parte de la ecuación.
Hace unos días esa herramienta apareció. TypeSafe, el laboratorio fundado por Diogo Almeida —que en OpenAI trabajó en los métodos de seguimiento de instrucciones que acabaron siendo la base de ChatGPT—, publicó Jev, el primer modelo de una categoría nueva a la que han llamado System One models. Y el nombre no es casual: Jev es por Jevons. La apuesta de la compañía es que, si decidir cuesta casi nada, decidiremos en todas partes. Veamos qué es exactamente y por qué tiene la forma de un if.
¿Qué es Jev?
Empecemos por el nombre de la categoría, porque explica casi todo. System One es una referencia directa a Daniel Kahneman y su Pensar rápido, pensar despacio: el Sistema 1 es el pensamiento rápido e intuitivo, el que nos dice sin esfuerzo que una cara está enfadada o que un correo huele a estafa; el Sistema 2 es el pensamiento lento y deliberado, el que resuelve una integral o planifica un viaje. Los LLMs actuales, con sus cadenas de razonamiento, son máquinas de Sistema 2. Lo que propone TypeSafe es una clase de modelo dedicada al Sistema 1: juicios rápidos, baratos y calibrados, pensados para vivir dentro del código y no para conversar con personas.
Jev es el primer modelo público de esa clase, y la forma más honesta de describirlo es como un if inteligente. Un if normal bifurca sobre valores que el ordenador puede calcular: if (order.total > 100). Funciona mientras la condición sea algo comprobable, y se rompe en cuanto la condición es un juicio: ¿este mensaje suena urgente?, ¿este comentario habla de facturación? Jev es el predicado que le faltaba a ese if, uno que sabe leer.
¿Cómo se usa un predicado así? Le entregamos dos cosas. La primera es un estado: el trozo de mundo sobre el que queremos decidir, que puede ser una cadena de texto, un objeto JSON o una lista. La segunda es un conjunto de preguntas tipadas: cada una declara de antemano la forma exacta de su respuesta, ya sea un sí/no, una opción de un conjunto que definimos nosotros o una posición en una escala que describimos nosotros. Jev responde a todas en una sola llamada y devuelve, para cada una, una decisión acompañada de la probabilidad de cada alternativa posible.
Ese detalle de las probabilidades es lo que distingue a Jev de un clasificador cualquiera. El modelo está entrenado para que sus probabilidades estén calibradas: cuando dice que algo es cierto con un 90 %, acierta aproximadamente el 90 % de las veces. No es una opinión que le hemos pedido que se invente, es una medida, y eso convierte la incertidumbre en algo sobre lo que podemos programar. Si la confianza es alta, el código actúa solo; si es baja, deriva a una persona o a un modelo más caro. El umbral es nuestro y está escrito en una línea que cualquiera puede leer y cambiar.
Y hay una consecuencia de todo esto que suena a truco de magia hasta que uno la piensa dos veces: Jev no puede alucinar, al menos en el sentido de inventar valores fuera del esquema. No porque sea más listo que los demás, sino porque no genera texto. El espacio de respuestas lo definimos nosotros al escribir las preguntas, y el modelo sólo reparte probabilidad entre las opciones que existen. No puede inventarse una quinta categoría de una lista de cuatro ni devolver un JSON mal cerrado, porque nunca llega a escribir ni una coma. La garantía de tipo no es empírica, es estructural. Otra cosa es que la opción que elija sea la correcta: eso sigue siendo un problema de precisión, pero ya no es un problema de formato.
A cambio, Jev renuncia a lo que un LLM hace mejor. No escribe, no resume, no explica su razonamiento, no genera código. Si necesitamos texto seguimos necesitando un modelo generativo; lo que cambia es que ya no tenemos que usarlo también para decidir. Y esa renuncia, precisamente, es la que le permite ser dos órdenes de magnitud más rápido y más barato. Para entender por qué, tenemos que mirar cómo produce sus respuestas, que es donde de verdad se separa de un LLM.
¿Qué diferencia a Jev de un LLM?
Volvamos a la cocina un momento. Un LLM produce su respuesta como quien dicta una carta: palabra a palabra, y cada palabra condicionada por todas las anteriores. Es el muestreo autorregresivo, y es a la vez su superpoder y su cuello de botella. Superpoder, porque una secuencia de texto puede ser cualquier cosa: una respuesta, un programa, un poema. Cuello de botella, porque incluso cuando la respuesta es una sola palabra, el modelo tiene que llegar a ella recorriendo el mismo camino secuencial, y en los modelos con razonamiento ese camino incluye cientos o miles de tokens de pensamiento previos que también pagamos y también esperamos.
Jev no dicta. Evalúa todas las preguntas del mismo estado a la vez, en paralelo, y lo que sale de cada una no es una secuencia sino una distribución de probabilidad sobre las opciones que hemos declarado. No hay primer token que condicione al segundo porque no hay tokens de salida; hay una lectura del estado y, por cada pregunta, un reparto de probabilidad. De ahí las cifras que TypeSafe publica: entre 70 y 500 milisegundos de extremo a extremo, frente a los segundos —a veces minutos— de un modelo de frontera con razonamiento activado. Y de ahí también el precio, porque los tokens de salida no se cobran por la sencilla razón de que apenas existen.
La segunda diferencia está en cómo se entrenó, y es la que menos se ve pero más importa para automatizar. Los modelos de chat se optimizan con RLHF o variantes: se les recompensa por producir respuestas que a un evaluador humano le gustan. Es un objetivo estupendo para conversar y bastante malo para decidir, porque a los humanos nos gustan las respuestas seguras de sí mismas, y un modelo entrenado así tiende a sonar convencido incluso cuando no debería. TypeSafe entrenó a Jev con lo que llaman Reinforcement Learning for Calibrated Decisions (RLCD), donde la recompensa no es gustar sino acertar con la probabilidad correcta. Es la diferencia entre un meteorólogo que dice "mañana lloverá" y uno que dice "70 % de probabilidad de lluvia" y, a lo largo del año, se cumple.
¿Y por qué importa tanto la calibración? Porque un modelo que acierta el 95 % de las veces pero no avisa cuando está en el 5 % restante no sirve para automatizar nada: hay que revisar el 100 % de sus respuestas para encontrar ese 5 %. Un modelo que acierta lo mismo pero señala sus dudas nos permite automatizar el 95 % y mandar el resto a una persona. La inteligencia es la misma; lo que cambia es que ahora sabemos dónde no fiarnos.
Hay que ser honestos con lo que esto no significa. Jev no es un LLM pequeño ni un LLM rápido: es otra cosa, con otro contrato. Un LLM es un generalista que puede hacer de todo con supervisión humana; Jev es un especialista que hace una sola cosa sin ella. Y la pregunta que a estas alturas se estará haciendo cualquiera que lleve un tiempo trabajando con estos modelos es la obvia: "pero yo ya obtengo decisiones tipadas de un LLM con salida estructurada, ¿en qué se diferencia esto?". Vamos a ello.
¿Qué diferencia a Jev de un LLM con salida estructurada?
La objeción es legítima. Cualquier proveedor ofrece hoy structured outputs, y librerías como el Vercel AI SDK lo empaquetan con Output.object(): definimos un esquema con Zod, el modelo devuelve un objeto y el SDK lo valida. Si sólo miramos el dato que llega al código, parece equivalente: ambos nos entregan { urgente: true } en lugar de una frase.
La diferencia está en el mecanismo, y el mecanismo es el que fija la factura y el reloj. Una salida estructurada no cambia cómo genera el modelo: sigue dictando token a token, y el esquema sólo restringe qué tokens son válidos en cada posición. Es texto con gramática. Por eso la generación puede fallar a medias, el razonamiento previo sigue ahí —cobrado y esperado—, y el esquema garantiza la forma del objeto pero no dice nada del proceso que lo produjo.
Ese último punto es el que importa. Si añadimos al esquema un campo confianza: number, el modelo lo rellenará con un 0.85 que sonará razonable, pero ese número es prosa disfrazada de dato: nada lo ata a la frecuencia real de aciertos. En Jev, en cambio, el esquema no es una guía sobre el texto, es el espacio de salida. El modelo no escribe un objeto que cumpla el esquema; produce directamente una distribución sobre sus opciones, calibrada por entrenamiento. La salida estructurada convierte texto en datos después de generarlo; Jev nunca genera texto, así que no hay nada que convertir.
Conviene decir que las cifras titulares de TypeSafe —cientos de veces más barato y más rápido— las presenta la propia compañía como el extremo alto de lo que veremos en la práctica. La forma de saberlo es probarlo con nuestros datos, y para eso hay que saber cómo se le habla. Veamos las tres primitivas que tenemos para preguntarle.
¿Cómo puedo trabajar con Jev?
Todo lo que se le puede preguntar a Jev cabe en tres primitivas, y cada una devuelve una respuesta con una forma distinta. Cada pregunta lleva un identificador que elegimos nosotros, unas instructions en lenguaje natural y, según el tipo, unos criteria que definen las opciones. Con el SDK oficial de JavaScript (@typesafe-ai/sdk) cada primitiva tiene su función de ayuda: noul, choice y score.
La primera es Noul, la pregunta de sí o no:
import { noul } from "@typesafe-ai/sdk"
const is_spam = noul("Is `message` unsolicited advertising or a scam?")
// { type: "noul", noul: 0.93 }
Devuelve un único número entre 0 y 1: la probabilidad de que la respuesta sea sí. Es la más sencilla y la que más fácil se usa mal: un 0.5 no significa "más o menos", significa que el modelo no sabe. Si lo que queremos medir es un grado, Noul no es la herramienta.
La segunda es Choice: una opción de un conjunto cerrado sin orden entre sus miembros. Qué equipo atiende el mensaje, en qué idioma está un fichero, cuál de estos cuarenta enlaces lleva a la página de precios.
import { choice } from "@typesafe-ai/sdk"
const department = choice("Which team should handle `message`?", {
billing: "Charges, invoices, refunds, subscriptions",
technical: "Bugs, outages, integration problems",
other: null,
})
// {
// type: "choice",
// choice: "billing",
// probabilities: { billing: 0.84, technical: 0.15, other: 0.01 },
// confidence: 0.6
// }
Devuelve la opción ganadora, la distribución completa sobre todas las opciones y una confidence que resume cuánto domina la ganadora sobre el resto. Dos consejos de la propia documentación: describir cada opción por lo que contiene y no sólo por su nombre, y dar siempre una salida (other) cuando la lista pueda no cubrir todos los casos, porque el modelo tiene que elegir algo.
La tercera es Score: una posición en una escala ordenada que describimos nosotros, de menor a mayor.
import { score } from "@typesafe-ai/sdk"
const frustration = score("How frustrated is the author of `message`?", [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry or threatening to leave",
])
// {
// type: "score",
// score: 1.3,
// probabilities: { "0": 0.0, "1": 0.7, "2": 0.3 },
// confidence: 0.54
// }
La respuesta es un score que puede caer entre dos peldaños, la probabilidad de cada peldaño y una confidence. Aquí la regla de oro es describir situaciones, no grados: "función rota pero con alternativa" le da al modelo algo contra lo que comparar; "moderadamente grave" no le da nada.
Las tres se envían juntas, contra el mismo estado, en una sola llamada, y el resto lo hace nuestro código. Es la parte aburrida, que es justo lo que queremos:
const client = new TypeSafeClient() // lee TYPESAFE_API_KEY del entorno
const { answers } = await client.systemOne({
state: { message },
questions: { is_spam, department, frustration },
})
if (answers.is_spam.noul > 0.9) return drop(message)
if (answers.department.confidence < 0.5) return routeToHuman(message)
if (answers.frustration.score > 1.5) escalate(message)
Y ahí está un hábito que cambia la forma de diseñar con Jev y que los agentes de código suelen hacer mal: preguntar todo de golpe. Con un LLM tendemos a hacer una pregunta, mirar la respuesta y decidir qué preguntar después, porque cada llamada cuesta. Con Jev cada pregunta se evalúa en paralelo contra el mismo estado y sólo cuesta sus propios tokens, así que lo razonable es lanzar en una única llamada todas las preguntas independientes que podamos necesitar —incluidas las que sólo importan para algunos mensajes— y dejar que el código elija cuáles usar. TypeSafe lo llama speculative fan-out, y en sus propias pruebas agrupar trece preguntas en una llamada salió doce veces más barato y diez veces más rápido que hacerlas en secuencia.
Para probarlo hay dos caminos. El directo es la lista de espera de typesafe.ai, con acceso a la consola, el playground y los SDKs de JavaScript y Python. El otro es el Vercel AI Gateway, donde Jev aparece como typesafe-ai/jev y se consume desde el AI SDK con experimental_evaluate, al mismo precio y sin esperar. Es el que yo usé para el experimento que viene ahora, y por eso el vocabulario del ejemplo cambia ligeramente: en el AI SDK, Noul se llama boolean. Todo lo demás es idéntico.
Un ejemplo práctico: el ticket de soporte que nadie lee a tiempo
Todos los formularios de soporte que he visto empiezan igual: un desplegable que le pide a la persona que diga de qué va su problema. Facturación, incidencia técnica, acceso a la cuenta, cancelar la suscripción. Y como ninguna lista de categorías está nunca completa, una última opción que significa ninguna de las anteriores: "Otro", "Algo más", "Consulta general". Esa última opción es donde ocurre esta historia.
Imaginemos una plataforma de streaming. El formulario ofrece siete motivos y el backend traduce el motivo elegido a una prioridad de ticket antes de entregarlo al helpdesk:
Es una tabla razonable. Alguien se sentó y la pensó: las cancelaciones son churn, los reembolsos son dinero, los problemas técnicos molestan pero se sobreviven, las ideas pueden esperar. Cada fila se gana su sitio. Excepto la última.
Un suscriptor escribe:
Pago el plan premium todos los meses pero no puedo ver nada en mi portátil. Cada título que abro se queda cargando para siempre y nunca arranca. En la tele funciona bien, así que no es mi suscripción.
¿Qué categoría elige? No es facturación, está pagando y lo sabe. No es acceso a la cuenta, está dentro. "Incidencia técnica" sería lo correcto, pero él no se ve a sí mismo con un problema técnico; se ve con un problema de ver la tele. Mucha gente en esa situación elige "Algo más", y en el momento en que lo hace su ticket entra en la cola con la prioridad más baja, detrás de cada petición de funcionalidad y de cada propuesta de partnership. Un cliente que paga y no puede usar el producto queda por debajo de alguien que pregunta si algún día se podrá ordenar la lista de favoritos alfabéticamente.
Aquí está la idea que tardé un tiempo en ver y que merece decirse en voz alta: la opción comodín no recoge mensajes de urgencia baja; recoge mensajes de urgencia desconocida. Tratar ambas cosas como si fueran la misma es el bug. Todas las demás filas de la tabla funcionan porque la categoría es la señal: quien elige "Cancelar suscripción" se está yendo, sea educado o esté furioso. Pero el comodín no lleva información por definición: es lo que eliges precisamente cuando ninguna categoría te describe. Para esa fila, lo único que sabe cómo de urgente es la petición es el propio mensaje. Y nadie lo estaba leyendo.
Los arreglos que no funcionan
Los primeros parches que se me ocurrieron tienen todos el mismo defecto: cambian el valor por defecto sin añadir información. Subir el suelo y hacer que "Algo más" entre en Media sólo mueve el sesgo, y ahora cualquier pregunta casual adelanta a una petición de funcionalidad seria. Preguntarle a la persona cómo de urgente es su caso dura un trimestre: quien más necesita ayuda es quien menos se atreve a reclamar prioridad, y quien descubre que "Urgente" responde antes lo marca siempre. Y el if ("refund" in message) de toda la vida aguanta hasta el primer mensaje que dice no quiero un reembolso, quiero que funcione, que la expresión regular escalará encantada.
La versión honesta del problema es que juzgar la urgencia de un párrafo en lenguaje natural es un juicio semántico. Es lo que un agente de soporte junior hace en dos segundos y una expresión regular no hace nunca. Y como este juicio ocurre dentro del envío de un formulario, mientras alguien espera su pantalla de confirmación, no puede tardar segundos ni devolverme una frase que tenga que parsear. Es, exactamente, un if con un predicado que requiere leer.
¿Choice o Score?
Mi primer instinto fue choice: cuatro categorías, elige una. Ese instinto estaba equivocado, y entender por qué es la mayor parte del diseño. La urgencia está ordenada: Baja está más cerca de Normal que de Crítica. Una pregunta choice no sabe eso; trata las cuatro etiquetas como nombres sin relación, de modo que confundir Baja con Crítica le cuesta al modelo exactamente lo mismo que confundir Baja con Normal. Es justo lo contrario de lo que necesita un sistema de triaje, donde la distancia entre peldaños es lo importante.
score está construido para escalas ordenadas, así que es la primitiva correcta. Definimos los peldaños de menor a mayor y describimos cada uno con el lenguaje que usaría el equipo de soporte. Ésta es la petición real, tal cual la envié a través del Gateway de Vercel:
{
"state": "I pay for the premium plan every month but I cannot watch anything on my laptop. Every title I open just spins forever and never starts. It plays fine on my TV, so it is not my subscription.",
"questions": {
"urgency": {
"type": "score",
"instructions": "How urgent is this support request?",
"criteria": [
"low: a general question, an idea, feedback, or a partnership enquiry",
"normal: a question or a problem the person can work around for now",
"high: the person is blocked, cannot pay, or is about to leave",
"critical: money or data already lost, a duplicate charge, or a legal threat"
]
}
}
}
Y la respuesta, en 380 milisegundos:
{
"answers": {
"urgency": {
"type": "score",
"score": 1.73,
"probabilities": { "0": 0, "1": 0.27, "2": 0.73, "3": 0 }
}
},
"usage": { "inputTokens": 401, "outputTokens": 18 }
}
El índice 2 es high. El suscriptor que no podía ver nada pasa del fondo de la cola al tercio superior, y nadie ha tenido que pedirle que se autoevalúe. Fijémonos en el 0.27 que queda sobre normal: el modelo no está seguro del todo y nos lo dice. Ve que es una experiencia rota, no una cuenta perdida. Ese matiz viene gratis, y podemos actuar sobre él o ignorarlo.
Para comprobar que la escala discrimina en las dos direcciones, dos mensajes de contraste con los mismos cuatro criterios:
El segundo importa tanto como el primero. Un sistema de triaje que lo escala todo es sólo una versión más ruidosa del sistema que no escala nada. La petición de la lista de favoritos sacó un cero rotundo con probabilidad 1: se queda al fondo, pero como juicio, no como valor por defecto.
La trampa: no redondear el score
Esta es la parte que no habría hecho bien sin leer los números con calma. El score que devuelve Jev es un promedio sobre toda la escala, ponderado por la probabilidad de cada peldaño. Se puede comprobar a mano sobre la respuesta anterior:
0×0 + 1×0.27 + 2×0.73 + 3×0 = 1.73
Lo que significa que un score puede caer entre dos peldaños por dos razones completamente opuestas:
{ "0": 0, "1": 0.5, "2": 0.5, "3": 0 } -> score 1.5
{ "0": 0.5, "1": 0, "2": 0, "3": 0.5 } -> score 1.5
El mismo número. En el primer caso el modelo duda entre dos vecinos y "más o menos alta" es una lectura justa. En el segundo, el modelo está dividido entre esto es trivial y esto es una emergencia, y redondear ese promedio a high inventa una respuesta que nunca dio. Es la peor lectura posible de un caso ambiguo, porque es la única que está mal en las dos direcciones a la vez.
Así que no redondeamos el promedio. Leemos la distribución y nos quedamos con el peldaño que concentra más probabilidad, rompiendo empates hacia arriba, porque en una cola de soporte los dos fallos no son simétricos: un ticket escalado de más le cuesta a un agente un vistazo; un ticket enterrado le cuesta al cliente su semana. El score lo guardamos igualmente, registrado junto al peldaño elegido. Cuando los dos discrepan, ése es exactamente el ticket que merece releerse.
El fallo es una decisión de diseño, no una excepción
Esta llamada vive dentro del envío de un formulario. Pase lo que pase con ella, una persona que pulsa "Enviar" tiene que acabar con un ticket. Por eso cualquier fallo —timeout, error de red, un no-200, un cuerpo que no parsea, credenciales ausentes— devuelve lo mismo: sin juicio. Un centinela, una rama. Y sin juicio significa el comportamiento antiguo: la tabla determinista sigue ahí y el comodín sigue cayendo en Baja. El clasificador sólo puede mejorar el statu quo; nunca puede romperlo.
El presupuesto de tiempo merece su propia decisión. Puntuar es rápido, pero abrir una conexión nueva no lo es, y superar el límite no es un ticket lento: es un mensaje urgente archivado con la prioridad más baja. Conviene dar margen a ese techo y reutilizar conexiones. Y los bots no se puntúan nunca: si el formulario tiene un honeypot, el descarte ocurre antes de la llamada al modelo, porque no hay motivo para pagar por clasificar spam.
Por último, el alcance se mantiene estrecho. Sólo el comodín va al modelo. Cada una de las otras categorías conserva su mapeo determinista, porque ahí la categoría sí es la mejor señal: una cancelación es riesgo de churn por muy alegremente que esté escrita. Lo que queda por ver es cuánto cuesta todo esto, y aquí es donde la comparación con un LLM deja de ser teórica.
Comparando costes
Hasta aquí las cifras de coste y latencia eran las que publica TypeSafe. Para el artículo quise las mías, así que lancé el mismo mensaje contra Jev y contra Claude Sonnet 5, los dos a través del Vercel AI Gateway, en la misma sesión y la misma red. El Gateway informa del coste a precio de lista de cada llamada, así que todo lo que sigue está medido, no estimado. Y para no hacer trampas con el LLM, no le mandé el JSON de Jev tal cual: le escribí el prompt que escribiría cualquiera de nosotros, con un system prompt que pide una sola palabra —low, normal, high o critical— seguido de los cuatro peldaños de la escala, y el mensaje del suscriptor como turno de usuario.
Fijémonos primero en la columna de entrada, porque juega a favor del LLM y aun así pierde: 401 tokens contra 195. Cargando el doble de tokens de entrada, Jev sale 25 veces más barato que la mejor configuración posible de Sonnet 5, porque la diferencia nunca estuvo en la entrada. Está en que un modelo de decisión no tiene salida que facturar.
El modelo que cambia de opinión
La fila más interesante no es la más cara, es la del medio. Con la configuración por defecto, Sonnet 5 obedece la instrucción y devuelve una sola palabra. Pero antes de escribirla piensa, y ese pensamiento cuesta entre 82 y 186 tokens de razonamiento facturados como salida, para una respuesta de tres. Seis ejecuciones idénticas dieron seis facturas distintas, de $0,00126 a $0,00230: un 80 % de variación por el mismo prompt, porque el coste depende de cuánto le dio por pensar esa vez. El coste de Jev es función de la longitud de la entrada y nada más, así que es el mismo número siempre.
Y lo que de verdad hace ruido: cinco de esas seis ejecuciones respondieron normal. La sexta, la única que no gastó ni un token de razonamiento, respondió high, el mismo peldaño que eligió Jev. Cuando le desactivamos el razonamiento de forma explícita, la respuesta es high en todas las ejecuciones, cuesta cuatro veces menos y tarda la mitad. Es una conclusión que merece un momento: si la tarea mejora cuando apagamos el razonamiento, la tarea nunca quiso un modelo de razonamiento. Estábamos pagando al modelo para que se convenciera de una respuesta peor.
La última fila es el hombre de paja, y la incluyo porque es la comparación que a todos se nos ocurre primero: pegarle a Sonnet el JSON de Jev y decirle que lo responda. El razonamiento que devuelve es bueno —de hecho es la explicación más completa de por qué el ticket es high que he leído—, pero son 232 tokens de prosa que nadie pidió, dentro de un bloque de markdown que hay que limpiar, bajo una clave que en una tanda se llamó score y en otra value. Un parser contra un objetivo móvil, a 167 veces el precio.
Dónde van los 401 tokens
Hay algo que no me cuadraba: 401 tokens de entrada para un mensaje de 190 caracteres. Así que hice cuatro sondas cambiando una sola cosa cada vez:
Hay un suelo de 271 tokens de andamiaje que se paga siempre, se pregunte lo que se pregunte: es la plantilla que convierte la respuesta en un valor tipado en lugar de en prosa. Declarar una escala ordenada añade 30; escribir los criterios como frases y no como etiquetas, 59 más, en cada llamada y para siempre; y el mensaje del cliente, la única parte que varía, son 41. El mensaje es el 10 % de la factura.
Esto tiene dos lecturas. La primera es que hay un mínimo de unos $0,0000114 por llamada que ninguna optimización baja. La segunda es la buena: el andamiaje es por petición, no por pregunta. Añadí al mismo mensaje dos preguntas más —si el problema es específico de un dispositivo, como boolean, y a qué equipo debería ir, como choice entre facturación, reproducción y cuenta— y la petición pasó a 485 tokens. Dos juicios adicionales por 84 tokens, frente a los 1.203 que habrían costado tres llamadas separadas. Y los dos acertaron: el problema es de dispositivo (0,93) y va al equipo de reproducción (probabilidad 1), dos cosas que un agente de soporte habría tenido que deducir leyendo. Es el speculative fan-out de antes, ahora con recibo.
Con todo esto, la conclusión de coste es la misma de siempre, pero ahora con números propios. A diez mil tickets al mes, el ahorro absoluto son entre cuatro y veintiocho dólares, y a nadie le importa. Ése no es el argumento. El argumento es que a $0,17 al mes el coste deja de formar parte de la decisión, y que la latencia de 839 milisegundos permite que la clasificación viva dentro de la petición HTTP en lugar de detrás de una cola. Ya no estamos sopesando si esta clasificación merece la pena; simplemente la ejecutamos, en todos los sitios donde ayude. Y eso, si lo pensamos, es exactamente lo que Jevons habría predicho.
Conclusiones
Empezamos con Jevons y con un vicio: usar el modelo más potente que tenemos a mano para cualquier cosa que huela a lenguaje natural. Es fácil dejarse llevar por él, y no por pereza. Un LLM funciona, está a una llamada de distancia y resuelve el problema; en un mundo donde la herramienta más potente también es la más cómoda, cuesta pararse a mirar qué forma tiene el problema antes de elegirla. Pero esa pausa es la mitad del trabajo. Un booleano, una opción de una lista cerrada o una posición en una escala no son problemas de generación, son problemas de decisión, y para ellos un modelo que dicta texto token a token es un cañón apuntando a una mosca.
Cuando hay costes por medio, resolver el problema no es la meta; la meta es resolverlo de la forma más eficiente. Y eficiente aquí no significa sólo barato. El experimento del ticket lo dejó claro en tres dimensiones a la vez: veinticinco veces menos coste en el mejor caso del LLM, la mitad de latencia —la suficiente para que la clasificación viva dentro de la petición y no detrás de una cola—, y una respuesta que no cambia según cuánto le dé al modelo por pensar esa vez. Lo que Jev aporta no es que decida mejor que un LLM, es que decide con la forma correcta: tipada, calibrada, en milisegundos y a un precio que deja de contar.
Y hay algo más general que el ticket. El bug del comodín tiene una forma que se repite en muchos sistemas: en algún sitio una persona eligió de una lista fija y nuestro código trata esa elección como un dato. Casi siempre lo es, hasta que una de las opciones significa ninguna de las anteriores. Campos "Otro", textos libres opcionales, valores por defecto que nadie cambió. Cada uno es un lugar donde el código decide en silencio que desconocido significa poco importante, y hasta ahora no lo arreglábamos porque lo único que llevaba la respuesta era un párrafo, y leer párrafos era caro. A $0,000017 y 839 milisegundos esa restricción ha desaparecido. Lo que queda es darse cuenta de dónde hicimos la suposición.
Me quedan dos hilos que este artículo sólo ha rozado y que merecen el suyo propio: el composite scoring, esa idea de descomponer un juicio complejo en varias preguntas independientes y combinarlas en código con pesos que controlamos nosotros; y el uso de Jev como router delante de un agente, decidiendo en cada turno qué modelo, qué herramienta o qué persona debe atender la petición. Los dos siguen la misma lógica que el ticket, sólo que un peldaño más arriba.
Enjoy!
Referencias