Módulo 3: Detectar Hallucinations en Código

Hallucinations en Imports y APIs

Hallucinations en Imports y APIs

Descripción de la cápsula

En la cápsula anterior clasificaste las hallucinations en 4 tipos. Ahora vas a profundizar en los dos primeros: imports falsos y APIs con signatures inventadas. Son los tipos más comunes — representan aproximadamente el 60% de las hallucinations en código generado por AI — y los más fáciles de detectar si sabes dónde mirar.

Esta cápsula te da un proceso sistemático para verificar imports y API calls. No se trata de memorizar cada función de cada librería — eso es imposible. Se trata de desarrollar el instinto de cuándo verificar y las técnicas de cómo hacerlo rápido.


Imports Falsos: El Caso Más Común

Por qué los imports son el target #1 de hallucinations

Los LLMs ven miles de imports durante el entrenamiento. Para librerías populares como FastAPI, pandas, sklearn, o requests, el modelo ha visto decenas de miles de combinaciones de imports. El problema: no todas esas combinaciones corresponden a la misma versión de la librería, y muchas combinaciones que el LLM genera son extrapolaciones de lo que ha visto, no copias de imports reales.

Anatomía de un import falso

from fastapi.security import OAuth2TokenValidator
#    ^^^^^^^^^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^
#    módulo real            clase inventada
#    (fastapi.security      (OAuth2PasswordBearer sí existe,
#     sí existe)            OAuth2TokenValidator no)

El patrón más común: el módulo es real pero la clase/función es inventada. El LLM sabe que fastapi.security existe porque lo ha visto cientos de veces. Pero inventa un nombre de clase que encaja con el patrón del módulo.

Los 5 patrones más comunes de imports falsos

Patrón 1: Combinación de conceptos existentes

El LLM combina dos conceptos reales en un nombre que no existe:

# Existen por separado:
from collections import OrderedDict
from collections import defaultdict

# ❌ El LLM combina ambos:
from collections import OrderedDefaultDict
# No existe — nunca se implementó en Python estándar
# Existen por separado:
from sklearn.metrics import roc_auc_score
from sklearn.preprocessing import MultiLabelBinarizer

# ❌ El LLM combina conceptos:
from sklearn.metrics import roc_auc_multiclass
# No existe — para multiclass se usa roc_auc_score con multi_class="ovr"

Patrón 2: Naming convention extrapolado

El LLM ve un patrón de naming y lo extiende:

# Existen en FastAPI:
from fastapi.security import OAuth2PasswordBearer
from fastapi.security import OAuth2AuthorizationCodeBearer

# ❌ El LLM extiende el patrón:
from fastapi.security import OAuth2TokenValidator
from fastapi.security import OAuth2RefreshTokenBearer
# El patrón OAuth2[X]Bearer / OAuth2[X]Validator parece lógico,
# pero estas clases no existen

Patrón 3: Submódulo que debería existir pero no existe

# json es un módulo estándar
import json

# ❌ El LLM asume submódulos que siguen convenciones de otras librerías:
from json.exceptions import JSONDecodeError
# "json.exceptions" no existe como submódulo
# ✅ Lo correcto: from json import JSONDecodeError
# logging tiene handlers
import logging

# ❌ El LLM inventa handlers específicos:
from logging.handlers import JSONHandler
# No existe — hay RotatingFileHandler, TimedRotatingFileHandler, etc.
# Para JSON logging se usa python-json-logger (paquete separado)

Patrón 4: Alias popular que no es el nombre real

# ❌ "Router" parece más intuitivo que "APIRouter"
from fastapi import Router
# ✅ Lo correcto:
from fastapi import APIRouter

# ❌ "JsonResponse" con esa capitalización
from fastapi.responses import JsonResponse
# ✅ Lo correcto:
from fastapi.responses import JSONResponse
# La diferencia: "Json" vs "JSON"

Patrón 5: Import de versión anterior

# ❌ Pydantic v1 style (deprecated en v2)
from pydantic import validator

# ✅ Pydantic v2 style:
from pydantic import field_validator

# ❌ httpx versión anterior:
from httpx import TestClient

# ✅ Para testing de FastAPI con httpx:
from httpx import ASGITransport, AsyncClient
# TestClient está en starlette: from starlette.testclient import TestClient

Proceso de verificación para imports

Cuando veas un import que no reconoces, sigue este proceso:

PASO 1: ¿Reconoces el import?
├── Sí → Probablemente correcto (pero verifica si hace mucho que no usas la librería)
└── No → Continúa a paso 2

PASO 2: ¿El módulo base es real?
├── python -c "import fastapi.security"
├── Si falla → Todo el import es falso
└── Si funciona → El módulo es real, verifica la clase/función

PASO 3: ¿La clase/función existe en el módulo?
├── python -c "from fastapi.security import OAuth2TokenValidator"
├── Si funciona → Import válido
└── Si da ImportError → Hallucination confirmada

PASO 4: ¿Qué SÍ existe en ese módulo?
├── python -c "import fastapi.security; print(dir(fastapi.security))"
└── Busca la alternativa correcta en el output

Verificación en terminal

# Verificar un import específico
python -c "from fastapi.security import OAuth2TokenValidator"
# Output: ImportError: cannot import name 'OAuth2TokenValidator'

# Ver qué existe en un módulo
python -c "import fastapi.security; print([x for x in dir(fastapi.security) if not x.startswith('_')])"
# Output: ['HTTPAuthorizationCredentials', 'HTTPBasic', 'HTTPBasicCredentials',
#  'HTTPBearer', 'OAuth2', 'OAuth2AuthorizationCodeBearer', 
#  'OAuth2PasswordBearer', 'OAuth2PasswordRequestForm', 
#  'OAuth2PasswordRequestFormStrict', 'OpenIdConnect', 'SecurityScopes']

# Verificar si un paquete está instalado
pip show pyjwt
# Output: Name: PyJWT, Version: 2.8.0, etc.

# Buscar una función en la documentación del paquete
python -c "import jwt; help(jwt.decode)"

APIs con Signatures Inventadas: El Caso Más Sutil

Qué los hace diferentes de imports falsos

Con imports falsos, la función/clase no existe. Python te da un ImportError inmediato. Con APIs inventadas, la función sí existe pero se usa con argumentos que no existen o con valores que no son válidos.

Esto los hace más peligrosos porque:

  1. El import no falla
  2. En algunos casos, el argumento extra es ignorado silenciosamente (con **kwargs)
  3. El error puede aparecer solo en runtime, cuando se llama la función con ciertos inputs

Anatomía de una API inventada

# La función existe
decoded = jwt.decode(token, secret, algorithms=["HS256"])  # ✅ Correcto

# La función existe pero con un parámetro inventado
decoded = jwt.decode(token, secret, algorithms=["HS256"], verify=True)  # ❌
#                                                         ^^^^^^^^^^^
#                                     parámetro inventado — "verify" no existe
#                                     lo real: options={"verify_signature": True}

Los 5 patrones más comunes de APIs inventadas

Patrón 1: Valor de argumento inventado

La función y el parámetro existen, pero el valor no es válido:

import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3]})

# ❌ orient="dict" no es un valor válido
result = df.to_json(orient="dict")

# ✅ Valores válidos:
# "split", "records", "index", "columns", "values", "table"
result = df.to_json(orient="records")

# Nota: df.to_dict() existe como método separado
# El LLM mezcló to_json(orient=...) con to_dict()

Patrón 2: Método encadenado que no existe

El LLM genera un método que encaja con la API de la librería pero no existe:

from sqlalchemy import select
from sqlalchemy.orm import Session

# ❌ .eager_load() no existe como método de Select
stmt = select(User).where(User.active == True).eager_load(User.posts)

# ✅ Lo correcto:
from sqlalchemy.orm import joinedload
stmt = select(User).where(User.active == True).options(joinedload(User.posts))
# ❌ .filter_by_date() no existe en QuerySet
users = db.query(User).filter_by_date(created_at__gte=start_date)

# ✅ Lo correcto (SQLAlchemy):
users = db.query(User).filter(User.created_at >= start_date)

Patrón 3: Constructor con parámetros de otro dominio

from pydantic import BaseModel, Field

class Product(BaseModel):
    # ❌ unique=True y index=True son conceptos de base de datos, no de Pydantic
    sku: str = Field(..., unique=True, index=True)
    name: str = Field(..., min_length=1, max_length=200)
    price: float = Field(..., gt=0)

# ✅ Pydantic Field() acepta: default, alias, title, description,
#    gt, ge, lt, le, min_length, max_length, pattern, etc.
#    Para constraints de DB: usar SQLAlchemy Column(unique=True, index=True)

Patrón 4: Función con signature de versión diferente

# ❌ En Pydantic v2, la API cambió
from pydantic import BaseModel

class User(BaseModel):
    name: str
    
    class Config:  # ❌ v1 style
        orm_mode = True

# ✅ En Pydantic v2:
class User(BaseModel):
    name: str
    
    model_config = {"from_attributes": True}  # v2 style
# ❌ pytest.raises con match como argumento posicional
import pytest

with pytest.raises(ValueError, "invalid input"):
    process_data(bad_input)

# ✅ Lo correcto:
with pytest.raises(ValueError, match="invalid input"):
    process_data(bad_input)

Patrón 5: Función real con tipo de retorno asumido incorrectamente

import os

# ❌ El LLM asume que os.getenv retorna siempre str
port: int = int(os.getenv("PORT"))
# Si PORT no está definido, os.getenv retorna None
# int(None) → TypeError

# ✅ Lo correcto:
port: int = int(os.getenv("PORT", "8000"))
# O con validación:
port_str = os.getenv("PORT")
if port_str is None:
    raise ValueError("PORT environment variable is required")
port = int(port_str)

Caso de estudio: La hallucination que costó 4 horas

Imagina este escenario real:

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allowed_origins=["http://localhost:3000"],  # ❌ HALLUCINATION
    allowed_methods=["GET", "POST", "PUT", "DELETE"],
    allowed_headers=["*"],
    allow_credentials=True,
)

El developer copia este código. Lo ejecuta. La app arranca sin errores. Pero CORS no funciona — el frontend en localhost:3000 sigue recibiendo errores de CORS.

¿Por qué? Porque el parámetro real es allow_origins (sin "d"), no allowed_origins. El parámetro extra es ignorado silenciosamente por **kwargs. CORS se configura con los defaults (que no incluyen localhost:3000).

El developer pasa 4 horas:

  1. Revisando headers del browser (1 hora)
  2. Buscando el error en el frontend (1 hora)
  3. Probando diferentes configuraciones de CORS (1 hora)
  4. Finalmente, leyendo la documentación oficial y comparando parámetro por parámetro (30 min)
  5. Encuentra allow_origins vs allowed_origins (30 min)

4 horas por una "d" extra. Ese es el costo de una hallucination de Tipo 2/3 que pasa silenciosamente.

Proceso de verificación para APIs

PASO 1: ¿La función existe?
├── python -c "import jwt; print(type(jwt.decode))"
├── Si da AttributeError → La función no existe (Tipo 1)
└── Si funciona → La función es real, verifica los argumentos

PASO 2: ¿Los argumentos son correctos?
├── python -c "import inspect; import jwt; print(inspect.signature(jwt.decode))"
├── Compara los parámetros que ves con los que el código usa
└── Cualquier parámetro que no aparece en la signature → sospechoso

PASO 3: ¿Los valores son válidos?
├── Busca en la documentación los valores aceptados
├── Para enums/opciones: la doc lista los valores válidos
└── Para tipos: verifica que el tipo del valor coincide

PASO 4: Verificar con un quick test
├── Ejecuta la función con los argumentos del código
├── Si da TypeError → Argumento no aceptado
├── Si funciona pero el resultado es inesperado → Valor incorrecto
└── Si funciona y el resultado es correcto → API es válida

Verificación en terminal

# Ver la signature de una función
python -c "import inspect; import jwt; print(inspect.signature(jwt.decode))"
# Output: (jwt, key='', algorithms=None, options=None, ...)

# Ver la documentación completa
python -c "import jwt; help(jwt.decode)"

# Quick test de un valor de parámetro
python -c "
import pandas as pd
df = pd.DataFrame({'a': [1]})
try:
    print(df.to_json(orient='dict'))
except ValueError as e:
    print(f'Error: {e}')
"
# Output: Error: Invalid value 'dict' for option 'orient'

# Verificar documentación oficial en terminal
python -c "import pandas; help(pandas.DataFrame.to_json)"

Estrategia: Cuándo Verificar y Cuándo Confiar

La regla del 80/20 para imports y APIs

No puedes verificar cada import y cada API call en cada archivo que AI genera — la productividad caería a cero. Necesitas una estrategia de cuándo verificar:

SIEMPRE verificar:
├── Imports que no has usado personalmente antes
├── Submodules específicos (from X.Y.Z import ...)
├── API calls con parámetros que no reconoces
├── Funciones de seguridad (auth, encryption, hashing)
└── Funciones de librerías que actualizaron recientemente

VERIFICAR SI HAY DUDA:
├── Imports de librerías que conoces pero de módulos poco comunes
├── API calls con valores de parámetros específicos
├── Métodos encadenados (.method1().method2().method3())
└── Funciones de testing con argumentos no-estándar

GENERALMENTE CONFIAR:
├── Import de módulos estándar de Python (os, json, datetime)
├── Import de la clase principal de un framework (FastAPI, BaseModel)
├── API calls que has usado cientos de veces
└── Funciones con signatures simples (1-2 argumentos conocidos)

Regla práctica: "Si no lo he escrito antes, verifico"

La regla más simple y efectiva: si es un import o API call que no has escrito tú personalmente al menos una vez, verifica. No importa si suena correcto. No importa si Claude Code lo generó con confianza. Si es la primera vez que lo ves, verifica.

Esta regla es conservadora al principio (verificas mucho) pero se relaja naturalmente con la experiencia: cada import verificado se convierte en uno que conoces para la próxima vez.


Hallucinations de Import por Librería

Top 5 librerías con más hallucinations de imports

Basado en patrones comunes de hallucinations en código Python generado por AI:

1. FastAPI / Starlette

# Hallucinations comunes:
from fastapi.security import OAuth2TokenValidator    # ❌
from fastapi import Router                           # ❌ (es APIRouter)
from fastapi.responses import JsonResponse           # ❌ (es JSONResponse)
from fastapi import QueryParam                       # ❌ (es Query)
from starlette.middleware import SessionMiddleware    # ❌ (es from starlette.middleware.sessions)

2. SQLAlchemy

# Hallucinations comunes:
from sqlalchemy.orm import relationship, backref, lazy_load  # ❌ lazy_load no existe
from sqlalchemy import Column, Integer, UniqueConstraint     # ⚠️ UniqueConstraint es real pero se importa diferente
from sqlalchemy.ext.asyncio import AsyncSession, async_session  # ❌ async_session como función no existe así

3. Pydantic

# Hallucinations comunes:
from pydantic import validator          # ⚠️ v1 — deprecated en v2, es field_validator
from pydantic import Schema             # ❌ no existe
from pydantic.types import EmailStr     # ❌ es from pydantic import EmailStr
from pydantic import ConfigDict         # ⚠️ Depende de la versión

4. sklearn / scikit-learn

# Hallucinations comunes:
from sklearn.metrics import roc_auc_multiclass           # ❌
from sklearn.preprocessing import TextVectorizer         # ❌ (es CountVectorizer o TfidfVectorizer)
from sklearn.model_selection import StratifiedKFoldCV    # ❌ (es StratifiedKFold)
from sklearn.ensemble import XGBoostClassifier           # ❌ (XGBoost es un paquete separado)

5. pytest

# Hallucinations comunes:
from pytest import mock                     # ❌ (es from unittest.mock o from pytest_mock)
from pytest import parametrize              # ❌ (es @pytest.mark.parametrize como decorator)
from pytest.fixtures import fixture         # ❌ (es @pytest.fixture como decorator)

Conexión con Proyecto

Cómo se manifiesta en el proyecto integrador

En el codebase del proyecto integrador (módulo 8), hay 1-2 imports falsos y 1 API inventada plantados. Ejemplos del tipo de hallucination que podrías encontrar:

# En el archivo de autenticación del proyecto:
from fastapi.security import OAuth2PasswordBearer  # ✅ Correcto
from fastapi.security import SecurityScopes         # ✅ Correcto
# Pero en otro archivo:
from fastapi.security import TokenValidator          # ❌ No existe

La técnica: cuando revisas un codebase, verifica todos los imports que no reconoces antes de pasar a la lógica. Es la verificación más rápida (1-2 segundos por import con python -c) y elimina las hallucinations más obvias primero.

Proceso para el proyecto

  1. Listar todos los imports únicos del codebase
  2. Marcar los que reconoces vs los que no
  3. Verificar los que no reconoces con python -c
  4. Anotar hallucinations encontradas con tipo y ubicación
  5. Pasar a verificación de APIs (siguiente nivel)

Troubleshooting

Problema 1: "Verifico un import pero me da error porque no tengo el paquete instalado"

Causa: No tienes la librería instalada localmente.

Solución: Primero verifica que el paquete existe buscando en https://pypi.org/project/[nombre]/ o ejecutando pip index versions [nombre]. Si el paquete existe, instálalo: pip install [nombre]. Luego verifica el import. Si el paquete no existe en PyPI, el paquete completo es una hallucination.

Problema 2: "El import funciona pero no estoy seguro de que sea la forma correcta"

Causa: Puede haber múltiples formas de importar la misma cosa.

Solución: Verifica la documentación oficial. Muchas librerías tienen imports deprecados que aún funcionan pero no son recomendados. Ejemplo: from pydantic import validator funciona en Pydantic v2 pero está deprecado. Que funcione no significa que sea correcto para tu versión.

Problema 3: "¿Cómo verifico APIs si la función acepta **kwargs?"

Causa: Funciones con **kwargs aceptan cualquier argumento sin error, haciendo la verificación más difícil.

Solución: Para funciones con **kwargs:

  1. Lee la documentación para saber qué parámetros son realmente procesados
  2. Busca en el código fuente de la función qué kwargs se extraen
  3. Haz un quick test: pasa el parámetro y verifica si tiene efecto
  4. Ejemplo: requests.get(url, verify_ssl=False) no da error pero verify_ssl no tiene efecto — verifica que SSL sigue activo

Problema 4: "Hay demasiados imports para verificar en un archivo grande"

Causa: Archivo con 30+ imports es overwhelming para verificar uno por uno.

Solución: Prioriza:

  1. Filtra imports de módulos estándar de Python (generalmente correctos)
  2. Filtra imports de la clase principal de cada librería (generalmente correctos)
  3. Enfócate en imports de submodules y funciones específicas
  4. Usa un linter como flake8 o ruff que detecta imports que no existen o no se usan

Problema 5: "El código usa una versión de la librería diferente a la que tengo"

Causa: La hallucination puede ser código válido de otra versión.

Solución: Verifica qué versión de la librería usa el proyecto (pip show [librería] o revisa requirements.txt). Luego verifica contra la documentación de esa versión específica. Lo que es correcto en v1 puede ser incorrecto en v2.


Ejercicios

Ejercicio 1: Verificar imports (Fácil)

Para cada import, determina si es correcto o una hallucination. Verifica con python -c si tienes duda:

# 1
from fastapi import FastAPI, HTTPException, Depends

# 2
from pydantic import BaseModel, Field, EmailStr

# 3
from sqlalchemy.orm import joinedload, selectinload

# 4
from fastapi.security import OAuth2PasswordBearer, OAuth2TokenValidator

# 5
from collections import OrderedDict, namedtuple, ChainMap
Ver solución
  1. ✅ Correcto. FastAPI, HTTPException, y Depends son imports reales de FastAPI.

  2. ✅ Correcto. BaseModel, Field, y EmailStr son imports reales de Pydantic. Nota: EmailStr requiere pip install pydantic[email] o pip install email-validator.

  3. ✅ Correcto. joinedload y selectinload son eager loading strategies reales de SQLAlchemy ORM.

  4. ⚠️ Parcialmente correcto. OAuth2PasswordBearer existe. OAuth2TokenValidator no existe — es una hallucination. Las clases reales en fastapi.security incluyen OAuth2PasswordBearer, OAuth2AuthorizationCodeBearer, HTTPBearer, HTTPBasic, SecurityScopes, etc.

  5. ✅ Correcto. OrderedDict, namedtuple, y ChainMap son todos parte de collections en Python estándar.

Ejercicio 2: Detectar la API inventada (Medio)

En cada snippet, hay una API call incorrecta. Encuéntrala:

Snippet A:

import pandas as pd

df = pd.read_csv("data.csv")
summary = df.describe()
json_output = df.to_json(orient="records")
filtered = df.query("age > 30")
sorted_df = df.sort_values(by="name", ascending=True)
unique_names = df["name"].unique_values()

Snippet B:

from pathlib import Path

config_dir = Path.home() / ".config" / "myapp"
config_dir.mkdir(parents=True, exist_ok=True)

config_file = config_dir / "settings.json"
content = config_file.read_text(encoding="utf-8")
config_file.write_text('{"key": "value"}', encoding="utf-8")
files = list(config_dir.iterdir())
size = config_file.file_size()
Ver solución

Snippet A: df["name"].unique_values() es incorrecto. El método real es df["name"].unique(). No existe unique_values() en pandas Series. El LLM generó un nombre más descriptivo pero incorrecto.

Snippet B: config_file.file_size() es incorrecto. El método real es config_file.stat().st_size. No existe file_size() en pathlib.Path. Si quieres el tamaño del archivo, necesitas llamar .stat() primero y luego acceder a .st_size.

Ejercicio 3: Verificar parámetros de API (Medio)

Estos API calls usan funciones reales. ¿Los parámetros son correctos?

# Call 1
import jwt
token = jwt.encode({"user": "alice"}, "secret", algorithm="HS256")

# Call 2
import requests
response = requests.get("https://api.example.com", verify_ssl=False)

# Call 3
from sqlalchemy import create_engine
engine = create_engine("sqlite:///db.sqlite", echo=True, pool_size=5)

# Call 4
import logging
logging.basicConfig(level=logging.DEBUG, format="%(message)s")
Ver solución

Call 1: ✅ Correcto. jwt.encode() acepta payload, key, y algorithm como parámetros. Nota que es algorithm (singular) en encode, no algorithms (plural, que se usa en decode).

Call 2: ❌ Hallucination. El parámetro verify_ssl no existe en requests.get(). El parámetro correcto es verify. requests.get("https://api.example.com", verify=False) es lo correcto. Lo peor: verify_ssl=False no causa error (se pasa como **kwargs) pero no tiene efecto — SSL verification sigue activa.

Call 3: ⚠️ Depende. echo=True es correcto. pool_size=5 es correcto para bases de datos que soportan pooling (PostgreSQL, MySQL). Para SQLite, pool_size no aplica porque SQLite no usa connection pooling de la misma manera. No dará error, pero no tiene el efecto esperado con SQLite.

Call 4: ✅ Correcto. level y format son parámetros reales de logging.basicConfig().

Ejercicio 4: Investigación de hallucination (Difícil)

Claude Code generó este código de autenticación. Sin ejecutarlo, identifica todas las hallucinations:

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.authentication import AuthenticationMiddleware
from jose import jwt, JWTError
from passlib.context import CryptContext
from pydantic import BaseModel, EmailStr
from datetime import datetime, timedelta
from typing import Optional

app = FastAPI()

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE = 30

class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: Optional[str] = None

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_data = TokenData(username=username)
    except JWTError:
        raise credentials_exception
    return token_data
Ver solución

Hallucination encontrada:

from fastapi.authentication import AuthenticationMiddleware  # ❌ HALLUCINATION

fastapi.authentication no existe como módulo. Esta es una hallucination Tipo 1 (import falso).

Si necesitas un middleware de autenticación en FastAPI:

  • Para OAuth2/JWT: usa fastapi.security (que ya está importado correctamente)
  • Para middleware de Starlette: from starlette.middleware.authentication import AuthenticationMiddleware
  • FastAPI no tiene su propio módulo authentication

El resto del código es correcto:

  • from jose import jwt, JWTError → Correcto (python-jose)
  • from passlib.context import CryptContext → Correcto
  • CryptContext(schemes=["bcrypt"], deprecated="auto") → Correcto
  • OAuth2PasswordBearer(tokenUrl="auth/token") → Correcto
  • jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) → Correcto
  • jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) → Correcto

Nota: AuthenticationMiddleware no se usa en ningún lugar del código después del import, lo cual es otra señal — un import no usado a menudo indica que fue generado por el LLM como parte del "patrón" de auth pero no es necesario.

Ejercicio 5: Construir checklist personal (Difícil)

Basándote en lo aprendido, construye un checklist de verificación de imports y APIs para una librería que usas en tu trabajo. El checklist debe incluir:

  1. Top 5 imports que verificarías siempre
  2. Top 3 API calls con parámetros fácilmente confundibles
  3. Proceso de verificación en 3 pasos
Ver solución (ejemplo con FastAPI + SQLAlchemy)

Top 5 imports para verificar:

  1. Cualquier import de fastapi.security (subclasses de OAuth2 son targets frecuentes)
  2. Imports de sqlalchemy.ext.asyncio (la API async tiene diferencias sutiles)
  3. Imports de pydantic en general (v1 vs v2 es fuente constante de hallucinations)
  4. Imports de starlette que no pasen por FastAPI (muchos se re-exportan, pero no todos)
  5. Imports de librerías de testing (httpx vs starlette.testclient vs pytest-asyncio)

Top 3 API calls confundibles:

  1. CORSMiddleware: allow_origins (no allowed_origins)
  2. Field() de Pydantic: no acepta unique, index, nullable (esos son de SQLAlchemy)
  3. create_engine(): pool_size (no max_pool_size)

Proceso en 3 pasos:

  1. python -c "from X import Y" → ¿El import existe?
  2. python -c "import inspect; print(inspect.signature(Y))" → ¿La signature es correcta?
  3. Quick test con valores del código → ¿El comportamiento es el esperado?

Resumen

En esta cápsula aprendiste:

  • Los imports falsos siguen 5 patrones: combinación de conceptos, naming extrapolado, submódulos asumidos, alias populares, imports de otra versión
  • Las APIs inventadas siguen 5 patrones: valores de argumento inventados, métodos encadenados falsos, parámetros de otro dominio, signatures de otra versión, tipos de retorno asumidos
  • El proceso de verificación para imports: verificar módulo → verificar clase → ver qué existe en el módulo
  • El proceso de verificación para APIs: verificar función → inspeccionar signature → verificar valores → quick test
  • La regla del 80/20: verificar siempre imports desconocidos y submodules; confiar en imports estándar y clases principales
  • La regla simple: "si no lo he escrito antes personalmente, verifico"
  • Las hallucinations de API con **kwargs son las más peligrosas porque fallan silenciosamente

Próxima cápsula: Hallucinations en Lógica — código que se ve correcto pero implementa algo diferente a lo que dice.


Recursos Adicionales

  1. Python inspect module - Cómo inspeccionar signatures de funciones programáticamente
  2. FastAPI Security Documentation - Referencia oficial para clases de seguridad
  3. Pydantic v1 → v2 Migration Guide - Guía de cambios entre versiones
  4. SQLAlchemy Documentation - Referencia para verificar ORM APIs
  5. PyPI Search - Verificar existencia de paquetes Python
  6. ruff — Python Linter - Linter rápido que detecta imports no existentes

Debugging & Code Review with Claude Code — Módulo 3, Cápsula 03 Claude Code Agentic Development Path — Guía #6 de 11