Umbrales y regresión
fijarás umbrales de regresión defendibles distinguiendo deterministas estrictos (pasa o no pasa) de jueces con margen (umbral más tolerancia, porque el juez tiene ruido), y razonarás baseline y deltas en vez de números arbitrarios. Sin esto, el gate de L4 cría falsos rojos o deja pasar regresiones reales.
5.1El umbral que mató el gate
Montaste el gate de L4. Corre en cada PR de Aurora. La primera métrica que pusiste fue la fidelidad del agente:
1- type: context-faithfulness
2 threshold: 0.85¿Por qué 0,85? Porque sonaba bien. Un número redondo, alto, serio.
La primera semana, el gate se pone rojo. Miras el PR: el cambio era inocuo —un retoque de estilo en el system prompt— y la fidelidad cayó a 0,83. No hay regresión real. Es ruido del juez.
La segunda semana, rojo otra vez. Otro PR limpio. Otro 0,84.
El equipo aprende rápido. "El gate de evals da falsos positivos." Empiezan a mergear con --no-verify. A las tres semanas, nadie mira el check rojo. Y entonces entra el PR que sí rompe la fidelidad de verdad —el reembolso fantasma vuelve—. Pasa sin que nadie lo note, porque el rojo ya era ruido de fondo.
Un umbral mal calibrado mata el gate igual que no tener gate. O bloquea todo y se desactiva, o no bloquea nada y es decorativo. El 0,85 "que sonaba bien" hizo lo primero.
Hoy aprendes a poner umbrales con criterio.
5.2Qué vas a poder hacer
Al terminar esta lección sabrás:
- Clasificar una assertion como determinista o de juez, y derivar de ahí el tipo de umbral que admite.
- Justificar un umbral estricto (0 tolerancia) para un check determinista y un umbral con margen para un check de juez.
- Razonar baseline y deltas, y reconocer qué soporta tu herramienta de forma nativa y qué no.
- Auditar un umbral dado y decir si es defendible o arbitrario, y por qué.
Necesitas saber antes:
- De N4·L2/L3, qué assertions son deterministas (
regex,is-json,contains) y cuáles son de juez (llm-rubric,context-faithfulness,FaithfulnessMetric). - Del checkpoint C2, que validaste tu juez contra humanos con TPR y TNR, y que ninguno llegó al 100%.
- De N4·L4, que el gate ya corre en CI y un check rojo bloquea el merge.
Esta lección no añade herramientas nuevas. Toma el harness que ya tienes y le pone los números correctos.
5.3Recupera
Antes de seguir, responde mentalmente. No mires lo de abajo hasta tener una respuesta.
- En el C2 validaste tu juez de Aurora contra etiquetas humanas. ¿Tuvo un TPR del 100% y un TNR del 100%? ¿Qué significa que no?
- De L2/L3, ¿cuál de estas dos puede fallar por "azar" sobre el mismo input repetido: una assertion
regexdel formatoorder_id, o unllm-rubricque juzga si la respuesta promete un reembolso? - Si repites el mismo caso de Aurora 10 veces contra el mismo
regex, ¿cuántos resultados distintos esperas? ¿Y contra el mismo juez?
La respuesta a la 1: no, tu juez no fue perfecto. En el C2 mediste TPR y TNR por debajo del 100% —el juez se equivoca a veces—. Langfuse recomienda exigir TPR y TNR por encima del 90% antes de producción, no del 100% (Langfuse, Automated evaluations, 12 sep 2025). El 100% no existe. Esa imperfección es la raíz de toda esta lección.
La 2 y la 3 son la misma idea: el regex es determinista —mismo input, mismo veredicto, siempre—. El juez es un evaluador con ruido. Diez ejecuciones del juez pueden dar varios resultados distintos. Esa diferencia decide qué umbral admite cada uno.
5.4El concepto: dos clases de umbral para dos clases de check
Empecemos por lo concreto —dos checks de Aurora— y subamos a la regla general.
Determinista estricto
Toma esta assertion de L2. La política de Aurora prohíbe prometer reembolsos no contemplados, así que vigilas que la respuesta no lo haga:
1- type: not-icontains
2 value: "he tramitado tu reembolso"Un check determinista es el que produce el mismo veredicto cada vez que lo corres sobre el mismo input. La analogía: una cerradura. La llave abre o no abre; no abre "un 84% de las veces". Esta analogía falla en que una cerradura puede desgastarse; un not-icontains no.
Este check admite un único umbral honesto: estricto, 0 tolerancia. Una sola conversación donde Aurora diga "he tramitado tu reembolso" es una regresión real, no ruido. No hay banda gris. O la frase prohibida aparece, o no. Pedir "que falle como mucho el 2%" sería absurdo: cada fallo es un cliente al que el bot le prometió dinero que no existe.
En promptfoo, este check no lleva threshold —es binario por construcción— (ver api-nivel4.md, §1.3: los tipos deterministas no aceptan threshold salvo excepciones como latency o levenshtein). Pasa o no pasa.
Juez con margen
Ahora la otra clase. Esta mide si la respuesta de Aurora se apoya en el contexto recuperado —tu fidelidad—:
1fidelidad = FaithfulnessMetric(threshold=0.85)FaithfulnessMetric es model-graded: por dentro, otro LLM juzga cuántos claims de la respuesta se sostienen en el retrieval_context (ver api-nivel4.md, §2.4). Y ya sabes del C2 que un juez no es un oráculo. Es él mismo un evaluador ruidoso.
Aquí está el error más común, y conviene nombrarlo: tratar el score del juez como si fuera una medida física exacta. No lo es. Si corres FaithfulnessMetric cinco veces sobre la misma respuesta de Aurora, puedes obtener 0,86, 0,84, 0,88, 0,85, 0,83. La respuesta no cambió. Cambió el veredicto del juez.
Por eso un check de juez no admite un umbral estricto. Necesita margen: un umbral más una banda de tolerancia que absorba la varianza del propio juez. Si fijas el umbral pegado a la media sin margen, el ruido normal lo cruzará y tendrás el rojo del §5.1.
El principio general, que une las dos clases:
El umbral es estricto cuando el check es determinista. Lleva margen cuando el check tiene ruido. El margen no es laxitud: cubre la varianza del evaluador, no la del sistema evaluado.
El umbral del juez no puede superar lo que el juez demostró
Hay un techo que mucha gente ignora. Tu juez de Aurora acordó con los humanos, digamos, un TPR del 92% en el C2. Eso significa que el juez se equivoca de vez en cuando, y tú lo mediste.
No puedes exigirle al gate más fiabilidad de la que el juez probó tener. Supón que el juez coincide con el experto humano el 92% de las veces. Entonces un umbral que pretenda detectar regresiones de menos del 8% mide, en gran parte, el ruido del juez —no la calidad de Aurora—. El acuerdo medido en el C2 es el suelo de tu resolución.
Baseline y deltas
Hasta aquí hablamos de umbrales absolutos: "fidelidad ≥ 0,85". Hay otra forma de pensarlo, y es la que el patrón de regresión recomienda.
El patrón de regresión tiene cuatro pasos. Parte de tu goldenset (el dataset versionado del C1). Córrelo con el prompt o modelo nuevo. Compáralo con el baseline —la medición de la versión anterior—. Bloquea el merge si la métrica cae más de X (DeepEval, Regression testing in CI/CD). El baseline es tu línea de referencia: no preguntas "¿es bueno?", preguntas "¿empeoró respecto a antes?".
Esta forma de pensar —baseline más delta— es más robusta que un absoluto. Un 0,85 fijo no sabe si tu sistema estaba en 0,95 y se desplomó, o estaba en 0,86 y subió. Un delta sobre baseline sí.
Aquí va un matiz honesto y obligatorio. La comparación contra un baseline previo no es nativa en DeepEval: DeepEval aplica un umbral absoluto, no un delta automático contra la ejecución anterior. Para comparar con baseline necesitas lógica propia (guardar el score anterior y compararlo) o una herramienta como Confident AI (DeepEval, Regression testing in CI/CD). No prometas un delta nativo que no existe.
En promptfoo hay una variable de entorno, PROMPTFOO_PASS_RATE_THRESHOLD, que fija un umbral de tasa de aprobados. Pero es global, no por test, y su comportamiento exacto está pendiente de verificar contra la doc oficial (ver api-nivel4.md, §3, y corpus E.1). Preséntala como lo que es: una palanca global, no un delta por métrica.
Los números de blog no son doctrina
Vas a encontrar tablas como esta: "fidelidad: caída máxima del 3%; relevancia: caída máxima del 5%; toxicidad: subida máxima del 1%". Circula en tutoriales (TestQuality, LLM regression testing pipeline, 2026, tier 4 — heurística de blog).
Trátala como punto de partida para conversar, no como autoridad. El número correcto para Aurora sale de tus datos: de la varianza que midas en tu juez y del acuerdo que validaste en el C2. Un 3% copiado de un blog no sabe nada de tu sistema. Cítalo como heurística, jamás como justificación.
5.5Míralo funcionar: la misma suite, dos umbrales razonados
Vamos a poner umbrales a dos checks de Aurora. Mismo harness, dos lógicas distintas, ambas defendibles.
Lee primero los dos bloques enteros. Después razonamos cada número.
Check 1 — determinista, 0 tolerancia
1# promptfooconfig.yaml — fragmento
2tests:
3 - vars:
4 system_prompt: "Eres un agente de soporte de Aurora."
5 mensaje: "Quiero el reembolso de mi pedido 88231"
6 assert:
7 - type: not-icontains
8 value: "he tramitado tu reembolso"
9 # sin threshold: es binario por construcciónCheck 2 — juez, umbral derivado del baseline con margen
Para el juez, primero mides su varianza. Corre la métrica varias veces sobre el mismo caso para estimar cuánto se mueve por ruido:
1from deepeval import assert_test
2from deepeval.test_case import LLMTestCase
3from deepeval.metrics import FaithfulnessMetric
4
5# 1) Caso del goldenset del C1
6caso = LLMTestCase(
7 input="¿Cuándo llega mi pedido 88231?",
8 actual_output="Tu pedido 88231 está en reparto, llega mañana.",
9 retrieval_context=["Pedido 88231: estado=en_reparto, ETA=2026-06-12"],
10)
11
12# 2) Baseline medido en la versión anterior del prompt: fidelidad media ~0.88
13# Varianza del juez estimada repitiendo la métrica N veces: ±0.03
14# Umbral = baseline - margen que cubre la varianza
15fidelidad = FaithfulnessMetric(threshold=0.85) # 0.88 - 0.03
16
17# 3) assert_test lanza AssertionError si la métrica cae bajo 0.85 -> test rojo -> CI rojo
18assert_test(caso, metrics=[fidelidad])Para correrlo:
1deepeval test run test_aurora_umbrales.pyAhora la pregunta de auto-explicación. Antes de leer el análisis, responde: ¿por qué el Check 1 va a 0 tolerancia y el Check 2 no puede?
El Check 1 es determinista. La frase prohibida aparece o no aparece —no hay un "84% de aparición"—. Cualquier fuga es una regresión real, nunca ruido. Por eso el umbral honesto es estricto: una sola aparición rompe el gate. Y por eso no lleva threshold: el tipo es binario por construcción.
El Check 2 mide con un juez ruidoso. El 0,85 no salió de "sonaba bien". Salió de dos números medidos: el baseline (0,88, la fidelidad de la versión anterior sobre el goldenset) menos un margen de 0,03. Ese 0,03 es la varianza que observaste repitiendo el juez sobre el mismo caso. El margen existe para que el ruido normal del juez no dispare el rojo. Si pusieras el umbral en 0,88 —pegado al baseline— el primer 0,86 por azar pondría el gate en rojo sin regresión real. Eso es exactamente lo que pasó en el §5.1.
La diferencia, en una frase: el determinista mide el sistema; el juez se mide a sí mismo y al sistema. Por eso el segundo necesita absorber su propio ruido.
5.6Hazlo tú
Ejercicio 1 — andamiaje parcial
Tienes estos datos medidos para una métrica de juez de Aurora —relevancia de la respuesta—:
- Baseline (versión anterior, sobre el goldenset): 0,90.
- Varianza del juez, estimada repitiendo la métrica 10 veces sobre el mismo caso: ±0,04.
Y una assertion determinista: regex que verifica que el order_id citado tiene el formato ^[0-9]{5}$.
Propón el umbral de cada uno y justifícalo en una frase:
- Determinista (
regexdelorder_id): umbral = __________ · porque __________. - Juez (relevancia):
threshold= __________ · porque __________.
Antes de seguir, una pregunta de interrogación elaborativa. Respóndela tú primero: ¿por qué estimar la varianza del juez exige repetir la métrica sobre el mismo caso, y no sobre casos distintos?
Piénsalo antes de leer la siguiente línea.
Porque sobre el mismo caso, el sistema evaluado no cambia. Cualquier variación en el score es ruido del juez, que es justo lo que quieres medir. Sobre casos distintos mezclarías dos fuentes de variación —la del juez y la de la dificultad de cada caso— y no podrías separarlas.
Ejercicio 2 — autónomo
Tu compañero ha definido tres umbrales para el gate de Aurora. Audita cada uno: marca cuál es defendible y cuál es arbitrario, y di por qué.
1# A) check de formato del order_id
2- type: regex
3 value: "^[0-9]{5}$"
4 threshold: 0.95
5
6# B) check de "no promete reembolsos"
7- type: not-icontains
8 value: "he tramitado tu reembolso"
9
10# C) check de fidelidad
11- type: context-faithfulness
12 threshold: 0.97Sin mirar la solución, escribe tu veredicto de A, B y C antes de pasar al §5.7.
5.7Comprueba
Sin pistas. Toma dos métricas de Aurora: una determinista y una de juez. Justifica un umbral defendible para cada una. Explica por qué uno es estricto y el otro lleva margen. No uses un número de blog como autoridad.
Las dos métricas:
- Determinista:
is-jsonsobre el argumento que el agente pasa abuscar_pedido(debe ser JSON válido). - Juez:
FaithfulnessMetricsobre las respuestas que citan una política de la KB. Baseline medido: 0,87. Varianza del juez estimada: ±0,03.
Ver la respuesta razonada
Determinista (is-json): umbral estricto, 0 tolerancia. El argumento es JSON válido o no lo es —no hay grado—. El mismo input da el mismo veredicto siempre. Una sola llamada con JSON inválido es un bug real que rompe buscar_pedido, nunca ruido. En promptfoo, is-json no lleva threshold: es binario. Cualquier fallo bloquea el gate.
Juez (FaithfulnessMetric): umbral con margen, derivado del baseline. El juez es un evaluador ruidoso: repetido sobre el mismo caso, su score se mueve ±0,03. El umbral defendible es baseline menos margen: 0,87 − 0,03 = 0,84. El margen absorbe la varianza del juez para que el ruido normal no dispare un falso rojo. No fijas 0,87 pegado al baseline, porque el primer 0,85 por azar pondría el gate en rojo sin regresión real.
Por qué uno es estricto y el otro no: el is-json mide solo el sistema y es reproducible al 100%; el juez se mide a sí mismo además del sistema, y su umbral debe cubrir ese ruido propio.
Feedback formativo:
- Si distinguiste estricto vs margen y derivaste el 0,84 del baseline menos la varianza: dominas el criterio central del nivel. Reutilizarás este razonamiento en N5, cuando el "baseline más delta" reaparezca como decisión estadística de A/B.
- Si pusiste un
thresholdalis-json(p. ej. 0,95): confundiste un check determinista con uno de grado. Un check binario no admite "el 95% de las veces"; o el JSON es válido o no lo es. Revisa el §5.4, "Determinista estricto", y la tabla deapi-nivel4.md§1.3: los tipos deterministas no llevanthreshold. - Si fijaste el umbral del juez en 0,87 sin margen: dejaste el umbral pegado al baseline. El ruido del juez (±0,03) lo cruzará en PRs limpios y tendrás el falso rojo del §5.1. El siguiente paso: resta la varianza medida al baseline y verbaliza por qué el margen no es laxitud, sino cobertura del ruido del evaluador.
Solución del Ejercicio 2 (auditoría):
- A — arbitrario. Un
regexes determinista: el formato encaja o no. Ponerlethreshold: 0,95no tiene sentido —sugiere que "el 5% puede fallar" cuando cadaorder_idmal formado es un fallo real—. Defendible sería quitar elthresholdy dejarlo binario. - B — defendible. Es determinista, sin
threshold, 0 tolerancia. Una sola promesa de reembolso es una regresión real. Correcto. - C — arbitrario por el techo del juez. Un
0,97de fidelidad exige al gate más resolución de la que el juez demostró tener en el C2. Si el juez acuerda con humanos el ~92%, un umbral del 97% mide sobre todo el ruido del juez. Defendible: derivarlo del baseline menos la varianza, respetando el acuerdo medido.
5.8Conecta
Vuelve al §5.1: el faithfulness >= 0.85 "porque sonaba bien" y los --no-verify.
Ahora ese mismo umbral está justificado, no inventado. El 0,85 sale del baseline (0,88) menos el margen (0,03) que mide el ruido del juez. Cuando el gate se ponga rojo, será una regresión real —no ruido—, y el equipo volverá a confiar en él. El gate de L4 deja de criar falsos rojos y deja de dejar pasar regresiones reales.
Esto cierra una pieza del checkpoint C4. Su rúbrica exige, en la dimensión 4, umbrales justificados —determinista estricto más juez con margen—, no arbitrarios (rúbrica C4.4, 03-arquitectura.md). Lo que practicaste aquí es exactamente esa dimensión.
En N4·L6 lo demuestras de punta a punta: un PR que cruza el umbral (rojo por regresión real) y uno que mejora (verde, sin falso rojo).
Y el criterio "baseline más delta" no se queda en N4. En N5 reaparece en producción. Decidir si una variante A/B supera a la actual es la misma pregunta —"¿mejoró respecto al baseline?"— resuelta con datos en vivo, no con un dataset offline.
¿Dónde lo aplicarías en tu trabajo? Piensa en cualquier gate de calidad que tengas hoy con un número fijo. ¿De dónde salió ese número? Si la respuesta es "lo pusimos y ahí se quedó", ya sabes qué auditar primero.
5.9Reflexiona
Tómate dos minutos. Estas preguntas consolidan más que releer.
- ¿Tus umbrales actuales saldrían de tus datos —baseline medido, varianza del juez— o de "lo que sonaba razonable"?
- ¿Cuántos
--no-verifyha generado ya un umbral mal puesto en tu equipo? ¿Era el umbral demasiado estricto, o demasiado laxo? - ¿Qué sigue sin estar claro? Si es "cómo guardo el baseline para comparar deltas", es la pregunta correcta —es la lógica custom que DeepEval no trae nativa—.
Referencia rápida
- Determinista estricto:
regex,is-json,contains,not-icontains→ veredicto reproducible → umbral de 0 tolerancia, sinthreshold(binario por construcción). - Juez con margen:
llm-rubric,context-faithfulness,FaithfulnessMetric→ evaluador ruidoso → umbral = baseline − margen que cubre la varianza del juez. - Estimar la varianza del juez: repite la métrica N veces sobre el mismo caso; la dispersión es ruido del juez, no del sistema.
- Techo del juez: no exijas al gate más resolución que el acuerdo TPR/TNR que el juez demostró en el C2.
- Patrón de regresión: goldenset → run nuevo → comparar con baseline → bloquear si cae > X (DeepEval). El delta contra baseline no es nativo en DeepEval (umbral absoluto); requiere lógica custom o Confident AI.
- promptfoo:
PROMPTFOO_PASS_RATE_THRESHOLDes global, no por test, y su comportamiento exacto está[por verificar]en la doc. - No usar como autoridad: las tablas de "caída máxima del 3/5%" de blog (heurística tier 4). El número correcto sale de tus datos.