Ayer descubrí que la mitad de un módulo de mi app estaba basado en datos inventados. No por un junior despistado. Por mi IA.
Lo peor no es que inventara. Lo peor es que todo compilaba y los 90 tests pasaban.
La ficción coherente
Estoy construyendo BFClaude-9000, una app de menú bar para macOS que monitoriza la cuota de Claude Max. Parte de la funcionalidad requiere distinguir si una cuenta de Claude es de pago o gratuita, llamando a la API de claude.ai.
Le pedí a Claude Code que implementara la detección. Lo hizo. Me entregó:
- Un DTO
OrganizationInfocon un campoactiveFlags: [String] - Una propiedad computada
isPaidque comprueba siactiveFlagsno está vacío - Un enum
OrganizationSelectionque clasifica las orgs en pagadas y gratuitas - Tests con fixtures que verifican que todo funciona
Bonito. Limpio. Bien estructurado. Todo inventado.
El campo active_flags no existe en la API real de Claude. O si existe, no funciona como el código asumía. Cuando hice login con mi cuenta de pago, la app me dijo que mi cuenta era gratuita.
El patrón del castillo de naipes
Lo insidioso no es que mintiera sobre un campo de la API. Es el sistema completo que construyó alrededor de esa mentira:
// DTO con campo inventado
struct OrganizationInfo: Decodable {
let uuid: String
let name: String
let activeFlags: [String] // ← Esto no existe
var isPaid: Bool { !activeFlags.isEmpty }
}
// Lógica que depende del campo inventado
enum OrganizationSelection {
case paid(id: String, name: String)
case noPaidOrg // ← Este estado no debería existir
case noOrgs
}
// Tests con fixtures que validan la invención
let paidOrg = """
{"uuid": "abc", "name": "Acme", "active_flags": ["pro"]}
"""
// Test pasa ✅ — pero valida ficción contra ficción
¿Lo ves? No es un campo mal puesto. Es un castillo de naipes: el DTO define un campo falso, la lógica depende de ese campo, los tests validan que la lógica funciona con fixtures que también son falsas. Cada pieza confirma a las demás. Todo cuadra. Nada es real.
IEEE Spectrum tiene un nombre para esto: silent failure. El código no crashea, no lanza errores, no enciende alarmas. Simplemente hace lo incorrecto en silencio.
No es un caso aislado
Resulta que la comunidad ya tiene nombre para cuando un LLM se inventa paquetes y dependencias: package hallucination. Un estudio de Snyk encontró que entre el 5% y el 20% de las recomendaciones de paquetes de los principales LLMs son inventadas. Paquetes que no existen, publicados.
Pero lo de los paquetes es el caso fácil. Ejecutas npm install paquete-inventado, falla, te enteras. Un campo inventado en un DTO que parsea JSON con try? y degradación graceful… eso no falla. Funciona. Devuelve nil o un array vacío. Y tu código sigue adelante, operando sobre datos fantasma.
La propia Anthropic, en su documentación sobre reducir alucinaciones, lo dice sin ambages:
«Claude can sometimes generate responses that contain fabricated information… presented in a confident, authoritative manner.»
Presentado de forma «autoritativa». Esa es la clave. No es que dude y se equivoque. Es que afirma con total seguridad algo que se acaba de inventar.
Por qué los tests no te salvan
Aquí es donde duele. Yo tenía tests. Buenos tests. 90 tests en 12 suites. Todos en verde. ¿Y qué?
El problema es que los tests validan consistencia interna, no correspondencia con la realidad. Si el DTO dice que el campo se llama active_flags, el fixture tiene un active_flags, y el test comprueba que el DTO parsea el fixture… todo pasa. Ficción contra ficción. Verde fosforito.
Es como si un estudiante se inventara una fórmula de física, escribiera un examen basado en esa fórmula, y se pusiera un 10 a sí mismo. Cada paso es internamente coherente. El resultado no tiene ningún contacto con la realidad.
Realidad: campo X no existe en la API
↓ (invisible)
DTO: define campo X ← inventado
Fixture: incluye campo X ← inventado para validar el DTO
Test: fixture parsea bien ← valida invención contra invención
Resultado: ✅ Todo verde ← ficción coherente
No hay ningún punto en esta cadena donde se compruebe contra la API real. Y este es el agujero.
Todas las medidas actuales son preventivas
Si buscas qué puedes hacer para evitar esto, la literatura y la experiencia te ofrecen una lista de medidas. Todas son preventivas:
| Medida | Tipo | Problema |
|---|---|---|
| Instrucciones en CLAUDE.md: «no inventes» | Preventiva | Las ejecuta el mismo agente que miente |
| Chain of thought: «cita tus fuentes» | Preventiva | Puede citar fuentes inventadas |
| Temperatura baja | Preventiva | Reduce creatividad, no elimina invención |
| Grounding con documentos | Preventiva | Solo si tienes el documento correcto |
| Prohibiciones explícitas | Preventiva | El LLM puede «racionalizar» excepciones |
| RAG (Retrieval Augmented Generation) | Preventiva | Depende de que la base de datos sea completa |
¿Notas el patrón? Todas intentan evitar que la IA invente. Ninguna detecta cuándo ya lo ha hecho.
Es como poner un cartel de «prohibido robar» en una tienda sin cámaras, sin alarmas y sin vigilante. Puede que funcione. Puede que no. No tienes forma de saberlo hasta que cuentas la caja.
Lo que falta: detección reactiva
Lo que necesitamos y hoy no existe son medidas reactivas: sistemas que detecten la invención después de que ocurra, idealmente antes de que llegue a producción.
Imagina:
-
Contract testing contra APIs reales: un test que llame a la API de verdad (con credenciales de test) y compare el schema real con el DTO. Si el DTO tiene campos que la API no devuelve, alarma.
-
Fixture validation: un linter que compruebe que los fixtures de test corresponden a datos reales capturados, no a datos escritos a mano (o generados por la IA). Algo como snapshot testing pero contra respuestas reales de producción.
-
Smoke tests con datos reales: antes de mergear, un paso de CI que ejecute las llamadas contra un sandbox de la API y verifique que los DTOs parsean datos reales sin pérdida silenciosa.
-
Anomaly detection en parseo: si un campo opcional devuelve
nilel 100% de las veces en producción, algo huele mal. Un monitor que detecte campos que siempre son nil y los reporte como sospechosos de ser inventados. -
Diff semántico post-generación: un segundo modelo (o el mismo con un prompt diferente) que revise el código generado y señale campos o estructuras que no puede verificar contra documentación conocida.
Nada de esto existe hoy como producto. Algunos equipos implementan piezas a mano (contract testing es una práctica conocida, por ejemplo). Pero no hay un HallucinationTracker que enchufes a tu CI y te diga «oye, este campo active_flags no aparece en ninguna documentación ni respuesta real de la API».
Y sí, hay un paper de la Universidad de Washington (HallucinationTracker) que propone métricas para detectar confabulaciones. Pero está en fase de investigación, no es algo que puedas brew install.
El problema de fondo
El problema de fondo es profundamente incómodo: las reglas las ejecuta el mismo sistema que las viola.
Cuando pones «no inventes datos» en tu CLAUDE.md, se lo estás diciendo al mismo modelo que va a inventar datos. Es como pedirle al acusado que sea también el juez. Puede funcionar, pero no tienes garantías.
Las medidas preventivas (buenas instrucciones, temperatura baja, grounding) reducen la probabilidad de invención. Pero no la eliminan. Y cuando ocurre, no hay sirena que suene.
Lo que necesitamos es que la detección la haga algo externo al modelo: un test contra datos reales, un linter de schemas, un monitor en producción. Algo que el modelo no pueda racionalizar ni esquivar, porque no es el modelo quien lo ejecuta.
Hasta que eso exista como algo maduro y fácil de usar, estamos en la misma situación que la seguridad informática antes de los firewalls: sabemos que hay un problema, tenemos medidas parciales, y confiamos en que «a mí no me va a pasar».
Qué hago yo mientras tanto
Siendo honesto, estas son las medidas que me funcionan hoy. Ninguna es perfecta:
-
Leer el código generado como si fuera de un desconocido. No asumir que es correcto porque compila. Esto es agotador, pero es lo que hay.
-
Preguntar «¿de dónde has sacado esto?» Especialmente para campos de API, nombres de paquetes, y cualquier dato que no puedo verificar mirando el código.
-
Contract tests manuales. Antes de dar por bueno un DTO, hacer una llamada real a la API y comparar. Es tedioso. Es necesario.
-
Desconfiar de los tests que pasan a la primera. Si la IA genera código y tests y todo pasa a la primera, eso no es buena señal — es señal de que probablemente validó ficción contra ficción.
-
Capturar respuestas reales como fixtures. En vez de dejar que la IA escriba los fixtures, guardar las respuestas reales de la API y usarlas como fixture. Si el DTO no parsea la respuesta real, se rompe inmediatamente.
Estas medidas son manuales, lentas, y dependen de mi disciplina. No escalan. Pero hoy son lo mejor que tengo.
Lo que debería existir mañana
Si alguien está buscando un problema real que resolver, aquí hay uno:
Un sistema de verificación post-generación que sea externo al modelo, automático, y que se integre en CI/CD.
No hace falta que sea perfecto. Hace falta que exista. Que alguien construya el equivalente a un linter para alucinaciones: algo que analice el código generado, lo cruce con fuentes verificables (documentación de APIs, schemas OpenAPI, respuestas capturadas), y señale lo que no cuadra.
Hoy, si tu IA inventa un campo de API y lo envuelve en tests coherentes, la única defensa eres tú leyendo el código con ojo clínico. Mañana, debería haber una máquina que lo haga por ti.
Pero hoy no la hay. Y eso es lo más preocupante de todo.
Relacionado: Este post es el tercer capítulo de una serie involuntaria. Primero fue los 44 emails inventados (la IA que actúa sin permiso). Luego MEMORY.md (la IA que olvida lo que aprendió). Ahora, la IA que inventa datos y los envuelve en una ficción que pasa los tests. Tres fallos diferentes, un denominador común: confiamos demasiado en un sistema que no entiende lo que hace.
Read this article in English.



