La parte 3 analizaba una herramienta que divide un juicio en otros más pequeños, un grafo de preguntas binarias en el que el camino recorrido decide la puntuación. La estrategia que toca ahora, la calibración probabilística, deja de dividir y se pregunta otra cosa. Ejecuta el juicio una sola vez e intenta medir cuánta certeza hay detrás de la respuesta.
Estoy construyendo AgentAnvil, un framework de pruebas guiado por contratos para agentes basados en LLM, y leer otros evaluadores me sirve para averiguar qué deberían restringir esos contratos. La calibración probabilística es la estrategia sobre la que más me ha costado formarme una opinión, porque dos herramientas llegan a ella por caminos bien distintos. DeepEval lee la incertidumbre dentro de una sola llamada al juez, en las probabilidades que el propio modelo asigna a los tokens. ARES coloca un intervalo de confianza formal alrededor de la puntuación que da el juez sobre un conjunto de evaluación. La primera devuelve un número más fino y la segunda, un intervalo que puedes defender.
En esta parte leo el código fuente de las dos.
La intuición
La incertidumbre de un juez LLM está en dos sitios, y cada herramienta va a por uno de ellos.
El primero está dentro de una sola llamada. Aunque la respuesta JSON dé otra impresión, el juez nunca se queda con un único entero. Lo que produce es una distribución sobre los enteros que ha considerado, con la mayor parte de la probabilidad en «4», algo en «3» y «5» y una pizca en el resto. La dispersión de esa distribución es la incertidumbre que expresa el juez, una señal de confianza aunque no esté calibrada. Un juez que pone el 99 % de su masa en «4» está seguro; uno que reparte un 35 %, un 30 % y un 25 % entre «3», «4» y «5» está dudando. El entero en bruto esconde los dos casos detrás del mismo número.
El segundo está entre llamadas. Si ejecutas el juez sobre mil entradas y promedias las puntuaciones, tienes la cifra de un benchmark. Si lo ejecutas sobre una muestra más pequeña con etiquetas de referencia humanas, tienes una medida de su sesgo. Al combinar las dos cosas puedes pasar de un punto a un intervalo, el intervalo de confianza que escribiría un estadístico. El juez sigue igual de ruidoso que antes y lo único que haces es acotar cuánto podría equivocarse la cifra agregada.
La función de DeepEval que consume el primer tipo de incertidumbre, calculate_weighted_summed_score, ya salió en la parte 3. Allí lo que importaba era que la ponderación por probabilidad recupera un número continuo a partir de una escala de enteros. Aquí la función hace otra cosa: la dispersión de la distribución es la confianza que expresa el juez, aunque el campo de la puntuación nunca la devuelva. ARES va a por el segundo tipo desde un ángulo que no tiene nada que ver, con la maquinaria estadística de un artículo de 2023 sobre prediction-powered inference. Son dos caminos para el mismo problema, con un código muy distinto en cada uno.
DeepEval: una distribución detrás de cada puntuación
Para puntuar un caso de prueba, GEval hace una sola llamada y en ella pide también las log-probabilidades del token de la puntuación. La llamada al proveedor necesita un endpoint compatible con OpenAI Chat Completions (las ramas del código para OpenAI, Azure, LiteLLM y las pasarelas OpenRouter/Portkey la repiten cada una por su cuenta, y el resto de proveedores se quedan con el entero en bruto). Los parámetros se pasan explícitamente.
completion = client.chat.completions.create( model=self.name, messages=messages, temperature=self.temperature, logprobs=True, top_logprobs=top_logprobs, **self.generation_kwargs,)top_logprobs es un parámetro del constructor de GEval que por defecto vale 20, el tope de la API de OpenAI, aunque DeepEval lo recorta según el modelo (a gpt-5.4, su juez por defecto, le deja 5). Para cada token generado, la respuesta trae hasta ese número de alternativas a las que el modelo daba alguna probabilidad. La distribución la pone el proveedor y GEval la consume.
Luego está el suelo de ruido. En la parte 3 ya vimos el cuerpo del bucle de calculate_weighted_summed_score, el que descarta los tokens con menos de un 1 % de probabilidad lineal y suma el resto según su peso. El umbral del 1 % es una variable local fija en el código.
# Filter out tokens with <1% linear probability, i.e., logprobs < math.log(0.01)min_logprob = math.log(0.01)No se puede configurar ni se expone, y ni siquiera es una constante. Es un número mágico metido en el cuerpo de la función. El punto en el que el ruido del juez deja de contar lo ha fijado el autor de GEval.
GEval.score no registra ni la distribución ni la confianza.Mueve el control de concentración. La puntuación ponderada se queda en 4,00 porque la distribución es simétrica y la ponderación por probabilidad respeta la simetría. Lo que sí cambia con la dispersión es la etiqueta de al lado, que salta entre confianza «alta» y «baja». Esa etiqueta es lo que GEval no devuelve.
A la salida de la métrica, todo se reduce a un número. La distribución pasa entera por calculate_weighted_summed_score y acaba convertida en un único escalar normalizado.
self.score = ( (float(g_score) - self.score_range[0]) / self.score_range_span if not self.strict_mode else int(g_score))self.success = self.is_successful() # score >= thresholdA partir de ahí, quien llama a la métrica puede leer score, success, reason, verbose_logs, los pasos de evaluación, el coste y el número de tokens, y ningún otro dato que salga de la distribución. No hay probabilidades por token, ni recuento de tokens descartados, ni entropía, ni segundo momento. La media ponderada es el veredicto, y el aprobado o el suspenso sale de compararla con el umbral. La distribución del juez ha alimentado la puntuación y luego se ha esfumado.
Para ser justos, GEval implementa fielmente la receta de Liu et al. (2023). La ponderación por probabilidad es el recurso que recupera una puntuación continua a partir de una rúbrica de enteros, y DeepEval la incorpora de forma limpia, con un filtro del 1 % comentado (aunque fijo en el código) y un valor por defecto razonable. El mecanismo es correcto. Lo que falta está en la interfaz con quien llama, porque la información solo existe mientras dura una llamada a una función y nada en la API la devuelve.
ARES: un intervalo de confianza alrededor de la puntuación
ARES se hace otra pregunta. Cambia «¿cómo de seguro está el juez de esta entrada concreta?» por «¿cómo de seguro puedo estar de la puntuación agregada, sabiendo que una pequeña parte tiene etiquetas de referencia?». El juez de ARES es, por defecto, un clasificador DeBERTa-v3-large ajustado, aunque se puede sustituir por un juez LLM. Sea cual sea, devuelve una predicción de 0 o 1 por cada ejemplo. Lo interesante del código es lo que ARES hace con esas predicciones.
Todo parte de tres arrays. En Y_labeled están las etiquetas de referencia humanas de un subconjunto pequeño anotado, normalmente unos pocos cientos de ejemplos; en Yhat_labeled, las predicciones del juez sobre esos mismos ejemplos, y en Yhat_unlabeled, sus predicciones sobre el conjunto de evaluación grande, el que no tiene etiquetas. Con eso, una sola función se encarga de toda la estadística.
def pp_mean_iid_asymptotic(Y_labeled, Yhat_labeled, Yhat_unlabeled, alpha): n = Y_labeled.shape[0] N = Yhat_unlabeled.shape[0] tildethetaf = Yhat_unlabeled.mean() rechat = (Yhat_labeled - Y_labeled).mean() thetahatPP = tildethetaf - rechat sigmaftilde = np.std(Yhat_unlabeled) sigmarec = np.std(Yhat_labeled - Y_labeled) hw = norm.ppf(1 - alpha / 2) * np.sqrt( sigmaftilde ** 2 / N + sigmarec ** 2 / n ) return [thetahatPP - hw, thetahatPP + hw]Ese es el estimador entero, y tiene tres piezas.
La primera es la estimación puntual. tildethetaf es la media del juez sobre el conjunto sin etiquetar, un número que arrastra el sesgo del juez si lo tiene. rechat es el error medio del juez sobre el subconjunto etiquetado, mean(Yhat - Y). Al restar uno de otro sale thetahatPP, la puntuación agregada del juez una vez descontado el sesgo que se le ha medido. Lo que hace posible la corrección es el subconjunto etiquetado, porque sin etiquetas de referencia no hay rechat ni rectificación.
La segunda es el intervalo. hw es la semiamplitud y suma dos términos: sigma_f² / N, del juez sobre el conjunto grande sin etiquetar, y sigma_rec² / n, del rectificador sobre el pequeño etiquetado. Como el conjunto sin etiquetar suele ser varios órdenes de magnitud mayor, su suelo es pequeño y el término que domina es el del rectificador. Con el alpha=0.05 por defecto, el multiplicador Z ronda 1,96, el del intervalo del 95 % de toda la vida.
n, y el otro, al crecer el conjunto sin etiquetar N. Al arrastrar los dos controles, el intervalo se estrecha y cambia el reparto de la varianza.Lo que añade ARES es el par [thetahatPP - hw, thetahatPP + hw], que se presenta junto a la estimación puntual (el punto medio del par). El ejemplo práctico del README imprime una predicción de 0,606 encima del intervalo [0.547, 0.664]. Ese 0,606 ya viene con el sesgo corregido (no es la media en bruto del juez). El intervalo es lo que permite a quien use el resultado leer «la puntuación agregada real está en este rango con un 95 % de confianza».
Hay dos rarezas que conviene señalar. El conjunto anotado por humanos se limita por defecto a 300 ejemplos (.head(300)), lo que pone un suelo fijo al término del rectificador. Y lo que parece una comparación con el intervalo clásico es código muerto: binomial_iid se llama, pero nunca se importa, así que solo sobrevive el intervalo PPI.
Para ser justos con ARES, la estadística que ha elegido es la primitiva adecuada para la pregunta que se hace. PPI es reciente (Angelopoulos et al. (2023)), y la mayoría de las herramientas de evaluación todavía no han asimilado que «tenemos un conjunto de referencia pequeño y un conjunto grande sin etiquetar» es un caso con un estimador ya publicado. ARES lo ha recogido y lo ha llevado al código. Solo por eso ya merece la pena leerlo.
Lo que pierdes
Los dos caminos tienen un precio, y cada uno lo paga a su manera. DeepEval lo paga en la interfaz. La distribución existe dentro de una llamada, la función la usa y después deja de existir para quien llama. Una puerta de CI que decide con self.score >= self.threshold no puede preguntar «¿ha sido un aprobado seguro o uno dudoso?», porque el campo que lo respondería no existe. ARES lo paga en etiquetas humanas, unos pocos cientos de ejemplos anotados (usa 300 como máximo y el README pide al menos 50). Para un artículo o un benchmark de cara a un regulador es un coste que se paga una vez; para una puerta de CI que se ejecuta en cada PR, es prohibitivo. Cada herramienta paga el coste que encaja con el uso para el que está pensada, y en ninguna de las dos es un descuido.
El prompt también las separa. La ponderación por probabilidad trata el prompt como algo fijo y no acota el eje de sensibilidad al prompt que señalaba la parte 1. Si reformulas la rúbrica, la distribución varía, pero la función sigue informando de la confianza dentro de ese prompt como si nada hubiera cambiado. PPI, en cambio, está anclado en otro sitio. Como son las etiquetas humanas las que definen el objetivo, reformular el prompt del juez cambia la amplitud del intervalo, pero no lo que el intervalo estima, siempre que el conjunto de referencia siga correspondiendo al sistema. La ponderación por probabilidad calibra dado el prompt. Calibrar entre prompts es otro problema que pide su propio artículo.
Cuándo encaja este enfoque
Qué camino tomar, o si conviene alguno, depende de una sola pregunta.
¿Lo que produces es una puntuación o una medida? Una puntuación es lo que lee una puerta de CI, un número para hoy y para esta PR. Una medida es lo que lleva un artículo, una tabla de clasificación o un informe de auditoría, es decir, una afirmación sobre el agregado que tiene que sostenerse cuando la examinen a fondo. Como primitiva de puntuación, la ponderación por probabilidad de DeepEval es mejor: es barata, necesita una sola llamada, no exige anotar nada y devuelve un número continuo que respeta las dudas del juez. El PPI de ARES, en cambio, es una primitiva de medida. Devuelve un intervalo cuya amplitud se puede discutir y cuyos supuestos se pueden auditar, pero el término del rectificador necesita unos pocos cientos de etiquetas de referencia, presupuestadas una sola vez. Cuantas menos tengas, más ancho sale el intervalo. Si no puedes pagarlo, PPI queda descartado.
Ninguna de las dos te da un juez cuyos veredictos se mantengan estables cuando reformulas el prompt. Si lo que ha cambiado entre despliegues es la rúbrica, DeepEval se adaptará sin avisar y dará cifras nuevas que no se pueden comparar con las anteriores. ARES sigue anclado a sus etiquetas de referencia, a costa de tener que mantenerlas al día. La siguiente parte analiza una herramienta que ataca ese problema sin medir la incertidumbre. Lo que hace es decirle al juez de antemano qué partes del juicio tiene que dejar fuera.
Si hay que quedarse con una regla, la ponderación por probabilidad es un adorno sobre un único juicio y PPI es inferencia estadística sobre una población de juicios. Elige la que trabaje al mismo nivel de análisis que la pregunta que te piden responder.