Aplicado a tus proyectos · Pipeline (TheLedger)
El gate que no deja que un LLM invente números
El Capítulo 1 te dijo que los LLM "alucinan con total confianza" y lo dejó ahí. Este es el problema resuelto en producción de verdad: dos capas de defensa, cada una cerrando el hueco que la otra no puede ver, disparadas por un incidente real con plata real de por medio.
Antes de leerHF cap. 11 §4, evaluación y LLM-as-judge · LLM 2026 · 4 §6, verificar fuera del modelo
🎯 Al terminar vas a poder
- Explicar por qué las alucinaciones son estructurales en un modelo autoregresivo, y distinguir fidelidad (faithfulness) de factualidad.
- Ubicar un gate determinístico, un verificador con citas y un LLM-as-judge dentro de la familia de defensas, con los límites de cada uno.
- Implementar una banda de precisión por cifras significativas y explicar por qué es más estricta que una tolerancia relativa.
- Aplicar fail-closed como principio de diseño, separando error de infraestructura de veredicto negativo.
- Diseñar un caso de aceptación para un pipeline propio a partir de un incidente real, en vez de un benchmark genérico.
0. El incidente Micron
Tu pipeline genera artículos financieros a partir de filings de la SEC con un LLM. El 2026-07-09, sobre un 10-Q de Micron Technology, ese LLM produjo dos afirmaciones falsas en la misma corrida — y son dos clases distintas de falla, que terminaron necesitando dos mecanismos de defensa distintos:
Fabricación pura
"Las ganancias se vieron infladas por un reembolso de aranceles único de $14.5 mil millones." Un evento que no existe en ningún lado del filing.
Número real, atribución falsa
El flujo de caja operativo del período actual declarado como $11.8 mil millones — cuando la columna del período actual en el filing decía $45.7 mil millones. $11.8B sí aparece en el filing: es el dato del año anterior.
La primera es fácil de imaginar cómo detectar: ese número no está en ningún lado del filing. La segunda es mucho más traicionera — el número sí existe, letra por letra, en el documento fuente. Solo está pegado a la columna equivocada.
Por qué esto es estructural, no un bug del modelo
Un LLM autoregresivo hace una sola cosa en cada paso: elegir el siguiente token según qué tan probable es dado el texto anterior. No tiene un paso separado de "verificar contra la fuente" — la fuente es solo más texto en el contexto, que influye en las probabilidades pero no las manda. "Un reembolso de aranceles único de $14.5 mil millones" es una frase perfectamente probable en un artículo financiero: tiene la forma correcta, el número tiene la magnitud correcta, y eso es todo lo que el modelo optimiza. Que el evento no exista no cambia la probabilidad de la frase. Por eso el entrenamiento no lo "cura": mejora la probabilidad de decir cosas ciertas, pero nunca la lleva a 1, y el modelo no tiene forma de saber en qué casos cayó del lado equivocado — de ahí la confianza total.
📐 Teoría de la información de la alucinación autoregresiva
En un modelo de lenguaje autoregresivo parametrizado por pesos , la probabilidad de una secuencia generada dado un documento de contexto se descompone por la regla de la cadena:
La fidelidad (faithfulness) formal se cuantifica como la información mutua entre la afirmación y el contexto , condicionada a los priors paramétricos :
Cuando el modelo incurre en una alucinación pura (como inventar «un reembolso de aranceles de $14.5B»), la distribución de predicción se desacopla del texto fuente:
Dado que el entrenamiento minimiza la divergencia de Kullback-Leibler sobre corpus masivos donde el estilo financiero fluido correlaciona con la sintaxis de balance contable, el modelo no optimiza una función indicatriz de respaldo documental: maximiza verosimilitud condicional. Por ello, la alucinación es un fenómeno intrínseco al muestreo sobre .
Dos palabras que conviene separar, porque los dos errores de Micron caen en lugares distintos:
| Propiedad | Pregunta que responde | Los dos errores de Micron |
|---|---|---|
| Fidelidad (faithfulness) | ¿Lo que dice el artículo está respaldado por el documento fuente que se le dio? | Los dos fallan: el reembolso no está en el filing; el $11.8B está, pero no dice lo que el artículo dice que dice. |
| Factualidad | ¿Lo que dice el artículo es cierto en el mundo? | Los dos fallan también — pero podrían no haberlo hecho: un artículo puede ser infiel al filing y aun así cierto (si el modelo "sabe" el dato de otro lado), o fiel a un filing que tiene un error. |
Todo lo que sigue en esta página verifica fidelidad: que cada número tenga respaldo en el filing que se le dio al modelo. Es lo único que un sistema puede verificar mecánicamente — la factualidad "en el mundo" no tiene una fuente contra la cual comparar. Y es lo que importa en este pipeline: el contrato con el lector es "esto sale del filing", no "esto es verdad porque el modelo lo dice".
1. Capa 1: el gate determinístico
numeric_gate.py no usa ningún LLM. Es puro regex y aritmética: extrae todos los números
del filing crudo en tres "pools" (exactos, escalables por unidad implícita, porcentajes), y después
chequea que cada número que aparece en el artículo generado tenga una contraparte en esos pools.
La primera versión usaba una tolerancia relativa fija (±0.8%) para permitir redondeos normales de prosa ("$15.3 mil millones" describiendo una tabla que dice "15,378"). Ese mismo margen es lo que dejó pasar el fraude de Micron: la tabla real tenía una línea "Additional capital 14,442" (en millones = $14.442B) — nada que ver con un reembolso de aranceles, pero a solo 0.4% de "$14.5 billion", dentro de la tolerancia.
🔧 El fix real: bandas de precisión
La v2 (post-incidente) reemplaza la tolerancia fija por una idea más simple y más estricta: un
número solo "funda" un reclamo si redondea o trunca a él, a la precisión que el
reclamo mismo declara. "$14.5 billion" tiene 3 cifras significativas → su banda es
[14.45B, 14.55B). 14,442 en millones es $14.442B — cae
afuera de esa banda por muy poco. Nunca podría redondear o truncarse a "14.5". La tolerancia
vieja medía distancia; la nueva pregunta es distinta: "¿este número podría legítimamente haberse
escrito así?"
📐 Formulación matemática del intervalo por cifras significativas
Para un número de reclamo expresado con cifras significativas, la magnitud decimal se expresa en notación científica como , donde y . La unidad en el último lugar decimal significativo (ULP) es:
El conjunto de valores de referencia que legítimamente redondean a define el intervalo semiabierto de aceptación:
Para el reclamo de Micron (, ): . El intervalo exacto es . Como el dato de la tabla era , queda rechazado automáticamente. En contraste, la tolerancia porcentual relativa con admitía , tolerando una desviación masiva de 232 millones de dólares.
Tres detalles de implementación que valen la pena por lo que enseñan de ingeniería de texto:
- Escalas implícitas: una tabla dice "15,378" con un header "(in millions)" en algún lado; la prosa dice "$15.4 billion". El gate prueba mil/millón/billón como escalas candidatas antes de descartar un número.
- Tokens "seguros" nunca cuentan: años (2026), enteros chicos (conteos, "10-Q"), nunca entran al pool ni se chequean como reclamos — de lo contrario cualquier año mencionado "fundaría" cualquier claim numérico cercano.
- Todo corre sobre texto, nunca sobre JSON parseado a mano: el mismo
_NUMERIC_TOKEN_REse aplica recorriendo cada string del payload — así el gate no necesita saber la forma exacta del JSON que genera el LLM, que cambia con cada versión del prompt.
2. Por qué la capa 1 no alcanza
Un gate por presencia, por más preciso que sea, tiene un techo estructural: solo puede preguntar
"¿este número existe en el filing?" — nunca "¿este número describe lo que el artículo dice
que describe?". $11.8 mil millones pasa el gate de la capa 1 sin problema: es un
número real del filing de Micron. El gate no tiene forma de saber que está en la columna del año
anterior, no en la del actual.
Leer una tabla financiera — entender qué columna es el período actual y cuál es el anterior, qué línea corresponde a qué concepto — es exactamente el tipo de tarea donde un LLM le gana a cualquier regex. El problema es el inverso: ¿cómo confiás en el juicio de un sistema que sabés que puede alucinar, para que sea el que certifique que otro LLM no alucinó?
La familia de defensas, y el límite de cada una
Esa pregunta tiene varias respuestas conocidas, y conviene ubicar la de tu pipeline entre ellas — ninguna es completa, y las buenas soluciones apilan varias:
| Técnica | Qué hace | Dónde se queda corta |
|---|---|---|
| Grounding con RAG | Le da al modelo la fuente en el contexto para que responda leyendo, no recordando. | Reduce alucinaciones, no las elimina: Micron es exactamente un modelo que tenía el filing en el contexto y fabricó igual. Grounding es la precondición de la verificación, no la verificación. |
| Atribución / citas | Exigir que cada afirmación venga con un pasaje de la fuente que la respalde (la idea de "attributable to identified sources", Rashkin et al.). | Un modelo puede inventar la cita con la misma confianza que inventa el dato. Solo sirve si alguien — código, no el modelo — comprueba que la cita existe y prueba lo que se dice. |
| LLM-as-judge | Un segundo modelo (o el mismo, en otra pasada) lee la salida y dicta un veredicto; variantes como Chain-of-Verification hacen que el modelo se haga preguntas de chequeo a sí mismo. | El juez alucina igual que el juzgado. Su juicio semántico es valioso (leer columnas de una tabla), pero su veredicto no es una prueba. Sin una verificación en código del respaldo, es voto, no evidencia. |
| Autoconsistencia | Muestrear varias respuestas y desconfiar de las afirmaciones que no se repiten (SelfCheckGPT). | Detecta inseguridad, no error: una fabricación que el modelo repite en todas las muestras pasa limpia. Y multiplica el costo de inferencia. |
| Gate determinístico | Código sin LLM que chequea propiedades mecánicas: números que existen en la fuente, formatos, rangos. | Solo ve lo que se puede expresar como regla. Existencia sí; significado no — es el techo del §2. |
Tu pipeline es la combinación de las últimas cuatro filas con el hueco de cada una tapado por la siguiente: grounding (el filing en el prompt), gate determinístico (capa 1), y un juez con citas cuya cita se verifica en código (capa 2). Lo que la sección siguiente explica es esa última pieza: qué significa que la verificación sea del código y no del modelo.
3. Capa 2: verificar con recibos, no por voto
La solución de claim_verifier.py es no confiar en el veredicto del LLM verificador por sí
solo. Se le muestra cada reclamo numérico junto a fragmentos copiados verbatim del
filing crudo, y se le pide una cita textual exacta que lo respalde — pero el código, no el modelo, es
quien decide si esa cita realmente prueba algo:
- Se buscan en el filing crudo todos los fragmentos que contienen un número compatible con el reclamo (misma lógica de bandas de precisión que la capa 1).
- El LLM recibe el reclamo + esos fragmentos, y debe responder con un veredicto (
supported/unsupported) más una cita exacta. - El código verifica dos cosas sobre esa cita, sin confiar en el modelo: que exista carácter por carácter (normalizando espacios) dentro de los fragmentos que el código mismo recortó, y que contenga un número que efectivamente matchea el reclamo.
🔧 Por qué esto es fail-closed contra un verificador que alucina
Un modelo que "alucina su verificación" — que inventa que vio una cita que nunca existió — no puede pasar: el string-match del código no va a encontrar esa cita inventada dentro de los fragmentos reales. El LLM aporta el juicio (¿esta columna es la correcta?); el código aporta la prueba (¿esa cita existe de verdad?). Ninguno de los dos solo alcanza.
El prompt al verificador es explícito sobre el problema exacto del incidente: "los estados financieros suelen imprimir el período actual en la primera columna numérica y los períodos anteriores a su derecha; un valor que aparece solo en una columna de período anterior no respalda un reclamo sobre el período actual." Esa instrucción no es genérica — está escrita para el error puntual que ya pasó.
4. Fail-closed en cada bifurcación
Todo el sistema comparte un principio: ante cualquier ambigüedad, no publicar es la opción por defecto. Pero hay una distinción fina que vale la pena remarcar, porque es fácil de pasar por alto: no toda falla es un veredicto.
| Situación | Tratamiento |
|---|---|
| El filing crudo no existe o está corrupto | Cuarentena — no se puede verificar, no se publica |
| El verificador responde JSON malformado | Cuarentena — respuesta inutilizable cuenta como "no soportado" |
| El verificador dice "unsupported" | Cuarentena — veredicto negativo real |
| La cita no matchea verbatim en los fragmentos | Cuarentena — el modelo no probó lo que dijo |
| El servidor vLLM del verificador no responde | No es cuarentena — VerifierUnavailableError, reintentar, artefactos intactos |
Esa última fila es la más sutil de todas: un timeout de vLLM no es lo mismo que un modelo diciendo "esto no está probado". Si un hipo de infraestructura borrara y descartara una síntesis válida como si el modelo la hubiera rechazado, terminarías perdiendo trabajo bueno por un problema que no tiene nada que ver con la calidad del contenido. La distinción entre "no pude verificar por una falla técnica" y "verifiqué y no está soportado" está modelada explícitamente en el tipo de excepción que se lanza.
Fail-closed y fail-open, como principio general
La disyuntiva no es de LLMs: es de cualquier sistema con un guardián. Fail-open significa que, si el guardián no puede decidir, deja pasar — la puerta de emergencia que se destraba cuando se corta la luz, o un antivirus que deja correr un archivo cuando no logra escanearlo. Fail-closed significa lo contrario: sin decisión, no hay paso — la bóveda del banco. Cuál corresponde depende de qué error cuesta más: en la puerta de emergencia, atrapar gente adentro es peor que dejar entrar a alguien; en la bóveda, al revés. Para un pipeline que publica cifras financieras con una firma, un artículo falso publicado cuesta más que un artículo bueno demorado, así que la elección es fail-closed en cada bifurcación.
Lo que la tabla de arriba agrega es la segunda mitad del principio, la que se suele olvidar: fail-closed no significa que toda falla se trate como un veredicto negativo. Hay que separar dos tipos de "no": "no pude evaluar" (infraestructura: timeout, servidor caído, red) y "evalué y falla" (veredicto). Los dos bloquean la publicación — eso es fail-closed — pero el primero conserva el trabajo y reintenta, mientras que el segundo lo manda a cuarentena. Si los confundís en una sola rama, cada hipo de red te destruye síntesis válidas, y con el tiempo alguien "arregla" eso relajando el gate — y ahí sí perdiste la garantía. Modelar la distinción en el tipo de excepción es lo que hace que el principio sobreviva a la operación diaria.
5. El harness que decide si un modelo nuevo puede servir
scripts/model_acceptance.py resuelve una pregunta que conecta directo con
tu pipeline de cuantización: cuando cambiás el modelo o la
cuantización que sirve de verificador, ¿cómo sabés que el nuevo sigue detectando lo que tiene que
detectar? La respuesta acá no es "correr un benchmark genérico y mirar el score" — es volver a correr
el incidente real de Micron contra el candidato.
La barra de aceptación tiene dos requisitos, y el segundo es tan importante como el primero:
- La síntesis de Micron tiene que fallar, y entre las fallas tienen que aparecer las dos firmas exactas del incidente: el "$14.5 billion" fabricado y el "$11.8 billion" mal atribuido. Esto último es lo que tu propio comentario en el script llama "the acid test" — detectar la mala atribución exige que el modelo lea los headers de columna de una tabla, algo que ningún gate determinístico puede hacer.
- Dos casos limpios conocidos (HIMS, VST) tienen que pasar, sin falsos positivos. Un modelo que rechaza todo es, para este propósito, tan inútil como uno que no rechaza nada.
🟢 La idea que generaliza
"Un modelo que no puede reproducir estos resultados no debe gatear producción, sin importar qué diga su porcentaje de recuperación en el benchmark" — tu propio comentario en el script. Es la misma lección que ya viste en la página de cuantización: un log que dice "salió bien" no es lo mismo que un artefacto verificado. Acá el equivalente es: un benchmark genérico que dice "95% de recuperación" no es lo mismo que reproducir el incidente real que te importa.
6. Bugs reales que ya encontraste
El código está lleno de comentarios fechados que documentan hallazgos de revisión adversarial — vale la pena leerlos como casos de estudio de lo difícil que es este problema en la práctica:
"16 basis points" → 16 mil millones
Sin un límite de palabra en el regex, el sufijo de escala ("b" de billion) se comía la primera letra de la palabra siguiente: "16 b[asis points]" se parseaba como "16 B". Un bug de regex, no de IA — pero envenenaba tanto el pool de referencia como los reclamos.
Una fecha como "prueba" de un reclamo en dólares
"2026" multiplicado por la escala implícita de millones cae dentro de la banda de un reclamo de "$2.0 mil millones". Los tokens "seguros" (años, conteos chicos) tuvieron que excluirse explícitamente de poder actuar como respaldo.
Metadata de producción leída como cifra financiera
Un cue de video a los 35 segundos, o la duración de una escena, son metadata de renderizado — no reclamos sobre el filing. 9 de 13 fallas del gate de media en una corrida real (E2) fueron por esto, antes de excluir esas claves.
El título de un artículo citado, leído como cifra del filing
Un artículo externo citado como fuente, titulado "...Up +27.9%...", exponía ese porcentaje como si el modelo lo hubiera narrado. Las claves de citación/atribución (source_map, web_sources) tuvieron que podarse como subárbol completo, sin importar su forma interna.
Ninguno de estos cuatro es un problema de "el LLM alucinó" — son todos formas en las que la infraestructura de verificación misma puede fallar en silencio si no se la revisa con la misma sospecha que al modelo que está vigilando.
7. Lo que generaliza
Sin filings ni Micron, esto es lo que vale para cualquier pipeline que publique salidas de un LLM:
| Lección | Acá | En cualquier otro pipeline |
|---|---|---|
| Verificá fidelidad, no "verdad" | Cada número tiene que tener respaldo en el filing | Definí cuál es la fuente contra la que se verifica; sin fuente no hay verificación, hay opinión. |
| Lo mecánico va en código; lo semántico en el modelo; la prueba, siempre en código | Regex para existencia, LLM para columnas, string-match para la cita | Nunca dejes que el modelo sea a la vez juez y notario: pedile el juicio y hacé que el código exija el recibo. |
| La tolerancia tiene que preguntar lo correcto | ±0.8% dejó pasar 14.442 como 14.5; la banda por cifras significativas no | Antes de poner un umbral numérico, escribí en palabras qué pregunta responde ("¿podría haberse escrito así?") y derivá el umbral de ahí. |
| Fail-closed, con dos tipos de "no" | VerifierUnavailableError vs cuarentena | Toda falla bloquea, pero infraestructura reintenta y veredicto descarta; modelalo en tipos, no en comentarios. |
| El incidente real es el test de aceptación | Micron tiene que fallar, HIMS y VST tienen que pasar | Cada incidente que te costó algo se convierte en un caso fijo que cualquier cambio de modelo, prompt o cuantización tiene que reproducir. |
| La infraestructura de verificación también falla en silencio | "16 basis points", fechas como respaldo, metadata de video, títulos citados | Revisá el verificador con la misma sospecha que al modelo: loggeá qué respaldó a qué, y leé esos logs. |
8. Resumen
El Capítulo 1 nombra el problema; esto es la respuesta de ingeniería completa:
- Capa 1 (determinística): ¿este número existe en el filing, a la precisión que el reclamo declara? Rápida, sin LLM, cero costo de inferencia.
- Capa 2 (verificación con recibos): ¿este número existente describe lo que el artículo dice que describe? Necesita juicio semántico (un LLM), pero el código exige una prueba verbatim que un modelo que alucina no puede falsificar.
- Fail-closed everywhere, con una distinción cuidadosa entre "no pude verificar" (infraestructura, reintentar) y "verifiqué y está mal" (veredicto real, cuarentena).
- Un harness de aceptación que gatea cualquier cambio de modelo o cuantización contra el incidente real, no contra un benchmark genérico — la disciplina de verificar el artefacto, aplicada a comportamiento en vez de a pesos.
Alucinar con total confianza deja de ser una limitación que solo "tenés que tener en mente" cuando hay una capa de código que no te deja publicar sin la prueba.
9. Ponete a prueba
Respondé antes de abrir cada respuesta. Si fallás una, volvé a la sección indicada.
1. ¿Por qué darle el filing completo en el contexto no alcanzó para que el modelo no fabricara el reembolso de aranceles?
Porque el modelo elige el token más probable dado el texto anterior, y el filing es solo más texto que influye en esa probabilidad — no un paso de verificación. Una frase con la forma y la magnitud correctas es probable aunque el evento no exista. Grounding baja la tasa, no la lleva a cero. (§0, §2)
2. Un artículo dice un dato cierto que no aparece en el filing. ¿Es fiel? ¿Es factual? ¿Qué hace el gate?
Es factual pero no fiel: no tiene respaldo en la fuente entregada. El gate verifica fidelidad, así que lo bloquea — correctamente, porque el contrato del pipeline es "esto sale del filing", y un sistema no puede verificar la verdad en el mundo, solo el respaldo en una fuente. (§0)
3. "$2.1 billion" en el artículo; la tabla dice "2,149" en millones. ¿Pasa la tolerancia del ±0.8%? ¿Pasa la banda de precisión?
Distancia relativa: 49/2100 ≈ 2.3% → no pasa la tolerancia vieja. Banda de "2.1" con dos cifras significativas: [2.05B, 2.15B) → 2.149B sí cae adentro, pasa. Es el ejemplo inverso a Micron: la banda no es "más estricta" en general, sino que responde otra pregunta — 2,149 sí podría escribirse legítimamente como 2.1. (§1)
4. El verificador responde "supported" con una cita que no aparece en los fragmentos. ¿Qué pasa y por qué eso es lo que protege contra un verificador que alucina?
Cuarentena: el código busca la cita carácter por carácter dentro de los fragmentos que él mismo recortó, y no está. El veredicto del modelo no cuenta sin prueba; una cita inventada no puede matchear texto real. El LLM aporta juicio, el código exige el recibo. (§3)
5. ¿Por qué un timeout del servidor vLLM no manda la síntesis a cuarentena, si el sistema es fail-closed?
Porque fail-closed significa "sin decisión no se publica", no "toda falla es un veredicto negativo". Un timeout es "no pude evaluar" — se bloquea la publicación, se conservan los artefactos y se reintenta (VerifierUnavailableError). Tratarlo como "evalué y falla" destruiría trabajo válido por un hipo de red, y con el tiempo llevaría a relajar el gate. (§4)
6. Un modelo candidato saca 97% en un benchmark de detección de alucinaciones pero no marca el "$11.8 billion" de Micron. ¿Puede gatear producción?
No. El harness exige reproducir las dos firmas del incidente real (fabricación y mala atribución) y no dar falsos positivos en HIMS y VST. Un benchmark genérico no prueba que el modelo lea columnas de una tabla; el incidente sí. Es la misma disciplina de "verificá el artefacto, no el log". (§5)
10. Para practicar
✏️ Ejercicio 1 — Bandas de precisión por cifras significativas
La idea del §1, aislada: dado un reclamo escrito como texto ("$14.5 billion"), devolver el intervalo
de valores que redondean a ese número tal como está escrito, y usarlo para decidir si un
valor del documento fuente lo funda. Python puro, sin dependencias. Decimal conserva
cuántos decimales escribió el autor — float no.
import re
from decimal import Decimal
ESCALAS = {"thousand": 10**3, "k": 10**3, "million": 10**6, "m": 10**6,
"billion": 10**9, "bn": 10**9, "b": 10**9}
# el \b final: "16 b" solo cuenta como "16 billion" si la "b" termina palabra
_RE = re.compile(r"\$?\s*(\d[\d,]*(?:\.\d+)?)\s*(thousand|million|billion|bn|k|m|b)?\b", re.I)
def banda(texto):
"""Intervalo [lo, hi) de valores que redondean al número tal como está escrito."""
m = _RE.search(texto)
cifra = Decimal(m.group(1).replace(",", ""))
escala = ESCALAS.get((m.group(2) or "").lower(), 1)
paso = Decimal(1).scaleb(cifra.as_tuple().exponent) # "14.5" -> 0.1 ; "15,378" -> 1
return (cifra - paso / 2) * escala, (cifra + paso / 2) * escala
def funda(valor, texto):
lo, hi = banda(texto)
return lo <= Decimal(valor) < hi
print(banda("$14.5 billion")) # (14450000000, 14550000000)
print(funda(Decimal("14442") * 10**6, "$14.5 billion")) # False: 14.442B no redondea a 14.5
print(funda(Decimal("15378") * 10**6, "$15.4 billion")) # True: 15.378B sí redondea a 15.4
print(abs(Decimal("14.442") - Decimal("14.5")) / Decimal("14.5")) # 0.004: el ±0.8 % lo dejaba pasar
print(_RE.search("16 basis points").groups()) # ('16', None): la "b" no se come
(Decimal('14450000000.00'), Decimal('14550000000.00')) · False · True · 0.004 · ('16', None)Pista / para seguir
Tres extensiones, en orden de dificultad: (1) el §1 dice "redondea o trunca" — agregá la banda de truncamiento [x, x + paso) y uní las dos; (2) "2,000" tiene exponente 0 en Decimal, así que la banda es [1999.5, 2000.5) — decidí qué querés que signifique un número con ceros finales y escribí el test; (3) excluí los "tokens seguros" (años entre 1900 y 2100, enteros chicos) tanto del lado del reclamo como del respaldo, y verificá que "2026" ya no pueda fundar "$2.0 billion".
✏️ Ejercicio 2 — Diseñá el caso de aceptación de tu propio pipeline
Sin código nuevo: tomá un pipeline tuyo que publique salidas de un LLM (un resumen, una
clasificación, un artículo, una extracción) y escribí su model_acceptance en un archivo
de texto, siguiendo el esqueleto del §5. Tiene que tener las dos mitades:
- Lo que tiene que fallar: el peor error real que ya viste (o el peor que podés imaginar, escrito como caso concreto con la entrada exacta y la firma exacta del error). Si no tenés un incidente real todavía, fabricá uno editando una entrada válida para plantarle un error — y anotá que es sintético.
- Lo que tiene que pasar: dos o tres entradas limpias, conocidas, que el modelo no puede rechazar. Sin esta mitad, un modelo que rechaza todo aprueba.
Después respondé por escrito: ¿qué cambio en tu sistema dispara esta corrida (modelo, prompt, cuantización, versión de librería)? ¿Cuánto tarda? ¿Quién mira el resultado? Si la respuesta a la última es "nadie", el harness no existe todavía.
Qué tendría que salir
Un archivo que cualquiera de tu equipo pueda leer y decir "sí, esos son los errores que nos importan". La prueba de que está bien diseñado: si mañana cambiás el modelo por otro que saca mejor puntaje en un benchmark público, el harness te dice en minutos si perdiste la detección que te importa — antes de que lo haga producción.
11. Fuentes y para seguir
- Ji et al. — Survey of Hallucination in Natural Language Generation (arXiv:2202.03629) — el mapa del problema: tipos de alucinación (intrínseca vs extrínseca), causas y métricas. Leelo para poner nombre a lo que el §0 explica.
- Rashkin et al. — Measuring Attribution in Natural Language Generation Models (arXiv:2112.12870) — define "atribuible a fuentes identificadas" (AIS), la formalización de lo que la capa 2 verifica con citas.
- Min et al. — FActScore: Fine-grained Atomic Evaluation of Factual Precision (arXiv:2305.14251) — descomponer una salida en afirmaciones atómicas y verificarlas una por una contra una fuente: la misma granularidad que tu gate aplica a cada número.
- Dhuliawala et al. — Chain-of-Verification Reduces Hallucination in Large Language Models (arXiv:2309.11495) — el modelo se genera preguntas de chequeo a sí mismo; útil para ver por qué el juicio del modelo ayuda y por qué no alcanza sin prueba externa.
- Manakul et al. — SelfCheckGPT: Zero-Resource Hallucination Detection (arXiv:2303.08896) — detección por autoconsistencia entre muestras, sin fuente; el contraste exacto con un pipeline que sí tiene fuente contra la cual verificar.