[Continuación del artículo anterior sobre detección de alucinaciones de código generado por IA.]( Tu linter no ve esto: alucinaciones de API y paquetes que no existen )
En el artículo anterior conté cómo nació la idea: un `requests.get_json()` que nunca existió, que pasó tests, linter y type checker, y que solo habría fallado en producción. La herramienta que construí entonces se apoyaba en un registro artesanal de APIs válidas: un mapa de librería → funciones conocidas, y una regla simple: si la librería está en el registro y la función no, es una alucinación. Funcionaba para `requests`, `json` y `os`, y detectaba paquetes inexistentes consultando PyPI.
Pero el registro tenía una limitación evidente: solo juzgaba lo que conoce. Y en cuanto salía de `requests`, `json` y `os`, la herramienta se quedaba muda. No porque no hubiera alucinaciones, sino porque no tenía contra qué comparar. En ese techo es que he venido pensando en estas semanas. Y Esto es lo que vino después.
Como es evidente, este trabajo no parte de cero. La investigación reciente confirma que las alucinaciones de librerías son sistemáticas (el 5,2% de los paquetes que mencionan los modelos comerciales no existen, y hasta el 21,7% en modelos open-source, asimismo, el análisis estático tiene un techo del 48,5% al 77% de lo que puede llegar a detectar , y que la verificación contra documentación real reduce falsos positivos de forma significativa . Nuestro proyecto trabaja algunas de esas limitaciones (paquetes inventados, APIs de paquetes reales verificadas contra wheels) y deja otras documentadas (APIs de paquetes inexistentes, slopsquatting avanzado). Esta continuación cuenta qué hemos hecho y qué queda.
¿Cómo verificar con más rigor?.
La pregunta que lo cambió todo fue: ¿y si en lugar de mantener a mano una lista de funciones válidas, leemos lo que el paquete realmente contiene?
PyPI no solo sirve paquetes: sirve wheels, que son archivos ZIP con el código y, muy importante, los type stubs (`.pyi`) cuando el paquete los incluye. Si descargas el wheel y lo abres, puedes buscar si `class DataFrame` existe en `pandas` sin instalar nada y sin ejecutar nada del código del usuario.
Eso es exactamente lo que hicimos. Y cambia el juego: ya no dependes de que alguien mantenga a mano una lista de funciones. Dependes de lo que el paquete realmente publica en PyPI. La cobertura deja de ser artesanal y pasa a ser la del ecosistema.
La caché, o cómo no castigar a PyPI en cada análisis.
El primer problema práctico fue obvio: si cada análisis consulta PyPI para cada import, en una red lenta como la mía misma, el análisis se eterniza. Y si PyPI cae, el detector se queda esperando. Así que montamos una caché en dos niveles (memoria + disco) con TTL:
- `exists` (¿el paquete existe en PyPI?): 7 días.
- Metadata de reputación (primera release, mantenedores): 30 días.
- Errores de red: 10 minutos (para no reintentar en bucle si la red va mal).
Y una decisión de diseño que ya venía del artículo anterior pero ahora se vuelve central: fail-open . Si no podemos verificar (timeout, PyPI caído), no reportamos el paquete como inexistente . Mejor un falso negativo puntual que un falso positivo que erosiona la confianza.
El resultado se nota: la segunda ejecución sobre el mismo archivo pasa de ~50 segundos a 17 milisegundos . Y todo eso sin tocar el flujo normal: si no configuras la caché, el detector funciona como antes.
Canonicalización, stdlib y el falso positivo que enseñó
Con la caché vinieron los detalles finos. PyPI normaliza nombres (`PIL` se instala como `Pillow`, `sklearn` como `scikit-learn`, `cv2` como `opencv-python`), y los imports traen submódulos (`tensorflow.keras`, `os.path`). Si no canonicalizas, el caché no acierta y aparecen falsos positivos raros.
Uno de los más instructivos: `imp` (un módulo de la stdlib, deprecado) se marcaba como typosquatting de `pip` . La distancia de edición era 1, y la heurística no sabía que `imp` es stdlib. La lección: las listas de stdlib y las de paquetes de PyPI tienen que estar sincronizadas , o el detector empieza a inventarse problemas donde no los hay. Hoy `imp`, `distutils` y compañía se reconocen como stdlib y no se marcan.
Verificar atributos de verdad: `pandas.DataFrame` ya no es un falso positivo
Aquí está el cambio más grande respecto al artículo anterior. Antes, `api-hallucination` era heurístico : miraba nombres sospechosos, no verificaba. Y eso generaba falsos positivos como `pandas.DataFrame`, que sí existe pero no estaba en el registro.
Ahora hay un modo profundo (`deep`) que, cuando lo activas:
1. Consulta PyPI para el paquete.
2. Descarga el wheel (una vez, cacheado por versión).
3. Abre el ZIP y busca el atributo en todos los `.pyi` y `.py` del paquete.
4. Devuelve uno de tres estados: confirmado , no encontrado o no verificable .
El resultado, medido con el mismo fichero de pruebas: 15 hallazgos sin modo profundo, 14 con modo profundo . La diferencia es exactamente el falso positivo de `pandas.DataFrame`, que desaparece. Y lo que es más importante: `pandas.funcion_inventada` sí se detecta, con la evidencia de que no está en el wheel real.
Eso es lo que el registro artesanal no podía dar. Y lo hemos hecho sin instalar Python en el servidor, sin ejecutar el código del usuario y sin añadir dependencias de terceros : solo HTTP, ZIP y un poco de regex.
El control por IP, o cómo abrirlo sin que te tumben el servidor
El modo profundo descarga wheels. Eso cuesta ancho de banda y tiempo. Antes de abrirlo al público, hacía falta un límite. Así que añadimos un control por IP : 3 análisis profundos al día por IP, con reset diario.
Tres detalles que nos parecían importantes:
- Localhost está exento. Para que las pruebas de desarrollo no se detengan por llegar al límite . Si no, te autobloqueas en tu propia máquina.
- Las IPs se pueden simular en desarrollo con `X-Forwarded-For` y una variable `DETECTOR_DEV=1`. Así se prueba el límite sin desplegar.
- En producción, `DETECTOR_TRUST_PROXY=1` para leer la IP real detrás de un proxy. Si no, todos los usuarios compartirían la IP del proxy.
El límite es simple, pero es la diferencia entre abrir el modo profundo con red y abrirlo sin red.
Los cuatro ficheros de prueba, y qué dicen de verdad
Para medir sin autoengañarnos, escribimos cuatro ficheros:
| Fichero | Qué mide | Resultado |
|---|---|---|
test_debe_detectar.py |
Recall (detectar lo malo) | 14–15 hallazgos, casi todos correctos |
test_no_debe_detectar.py |
Precisión (no marcar lo bueno) | 0 hallazgos |
test_limitaciones.py |
Falsos negativos/positivos conocidos | 1 hallazgo (un TP por heurística) |
test_syntax_error.py |
Sintaxis rota | 0, “archivo limpio” |
Lo bueno: la precisión es perfecta en código limpio. Lo honesto: hay un bug de UX en sintaxis rota (el detector dice “limpio” cuando el archivo ni siquiera compila) y hay falsos negativos estructurales (imports dinámicos, `from-import` de nombres, submódulos inexistentes) que requieren análisis más profundo. Los tenemos documentados, no escondidos.
Así pues, el proyecto ya no es “un registro de APIs”. Es un detector que verifica contra PyPI de verdad , con caché, modo profundo opt-in y control de uso. De este hito queda por revisar:
- Eventos de descarga de wheel en la barra de progreso (hoy, con modo profundo, la barra se congela mientras baja el wheel).
- `–deep` en el CLI , para que la terminal tenga la misma potencia que la web.
- Reportar `syntax-error` como hallazgo, en vez de decir “archivo limpio”.
- Evidencias y estados en cada hallazgo (`confirmado`, `probable`, `no verificable`).
- pyright/mypy solo como opción local , nunca en el servidor (lento, requiere instalación, y sin stubs no marca nada).
Y hay una cosa más que nos gustaría contar. Cuando tengamos hosting, (complicado desde mi país bajo asedio) subiremos una web que ya permite hacer los análisis online. Es una herramienta que ya está construida y funcionando en local: subes un .py, ves el progreso en tiempo real (con barra de progreso vía SSE) y obtienes los hallazgos, incluido el modo profundo con verificación contra wheels de PyPI. Pero para que esa web sea sostenible y no se caiga a la primera de cambio, necesitamos una contribución al desarrollo de la herramienta. No es un pago por usar el servicio: es una forma de que el proyecto pueda pagar el hosting, el ancho de banda de las descargas de wheels y el tiempo de mantenimiento. La idea es que quien quiera usar el modo profundo de forma intensiva pueda contribuir, y que quien solo quiera probar el análisis básico pueda hacerlo sin barreras. El código seguirá siendo abierto; la web con hosting será la forma de que el proyecto se sostenga. Lo contaremos con más detalle cuando esté listo.
El artículo anterior terminaba con una idea: “el linter ve lo que conoce; el type checker ve lo que está en la firma; ninguno sabe si el método existe de verdad”. Este es el intento de cerrar ese hueco. No con una lista más grande, sino leyendo lo que el paquete publica . Sigue sin ser perfecto: hay falsos negativos que reconocemos y un bug de sintaxis pendiente. Pero la diferencia entre “no hay alucinación” y “no se pudo verificar” ya no la escondemos: la medimos, la documentamos y la intentamos reducir.
Si pruebas el modo profundo y encuentras un falso positivo o uno que se te escape, me interesa saberlo. Este proyecto se corrige con lo que encuentran otros.
Referencias
1. Spracklen, J., Wijewickrama, R., Sakib, A.H.M.N., Maiti, A., & Viswanath, B. (2025). *We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs*. 34th USENIX Security Symposium. arXiv:2406.10279 .
2. Miranda-Pena, C., Reeson, A., Paris, C., Poon, J., & Kummerfeld, J.K. (2026). *An Empirical Analysis of Static Analysis Methods for Detection and Mitigation of Code Library Hallucinations*. arXiv:2604.07755 .
3. Tileria, M., et al. (2026). *Hallucination Inspector: A Fact-Checking Judge for API Migration*. arXiv:2604.20202 .
4. Raj, A., & (coautor). (2026). *Names Can Hurt: Spotting Slopsquatting Risks Caused by Package Name Hallucinations in Local Coding LLMs*. arXiv:2608.23897 .
5. Churilov, A. (2026). *The Range Shrinks, the Threat Remains: Re-evaluating LLM Package Hallucinations on the 2026 Frontier-Model Cohort*. arXiv:2605.17062 .