Módulo 3: Detectar Hallucinations en Código
Herramientas de Detección de Hallucinations
Herramientas de Detección de Hallucinations
Descripción de la cápsula
Tu ojo humano es la primera línea de defensa contra hallucinations — pero no es infalible. En las cápsulas 03 y 04 entrenaste tu ojo para detectar imports falsos, APIs inventadas, parámetros incorrectos, y lógica fabricada. Ahora vas a construir tu red de seguridad automatizada: herramientas que detectan lo que tu ojo no ve.
Esta cápsula te da un toolkit concreto. No teoría sobre qué herramientas existen — sino comandos exactos, configuraciones, y procesos que puedes usar mañana. Al terminar, tendrás un proceso de verificación en 4 capas que atrapa hallucinations en cada nivel.
Las 4 Capas de Verificación
El modelo de defensa en profundidad
Ninguna herramienta detecta todos los tipos de hallucinations. Por eso usas capas: si una falla, la siguiente atrapa el error.
Capa 1: Linters estáticos (ruff, flake8, pylint)
├── Detecta: imports no existentes, variables no definidas,
│ syntax errors, imports no usados
├── Tipo que atrapa: Tipo 1 (Imports falsos) — parcial
├── Tiempo: < 1 segundo
└── Esfuerzo: 0 (se ejecuta automáticamente)
Capa 2: Type checkers (mypy, pyright)
├── Detecta: tipos incorrectos, atributos no existentes,
│ signatures incompatibles
├── Tipo que atrapa: Tipo 2 (APIs inventadas) — parcial
│ Tipo 3 (Parámetros incorrectos) — parcial
├── Tiempo: 2-5 segundos
└── Esfuerzo: configuración inicial
Capa 3: Quick tests (pytest, python -c)
├── Detecta: lógica incorrecta, edge cases,
│ comportamiento inesperado
├── Tipo que atrapa: Tipo 4 (Lógica fabricada) — parcial
│ Tipo 2 y 3 — en runtime
├── Tiempo: 1-5 minutos (escribir + ejecutar)
└── Esfuerzo: medio (escribir tests)
Capa 4: Documentación oficial
├── Detecta: todo lo que las otras capas no atrapan
├── Tipo que atrapa: Todos los tipos
├── Tiempo: 2-10 minutos
└── Esfuerzo: alto (leer y comparar)
La idea: las capas 1 y 2 son automáticas y rápidas. La capa 3 requiere esfuerzo pero es la más efectiva contra lógica fabricada. La capa 4 es el último recurso para verificaciones que ninguna herramienta puede hacer.
Capa 1: Linters Estáticos
ruff — el linter rápido de Python
ruff es el linter más rápido para Python. Detecta cientos de tipos de errores en milisegundos.
Instalación:
pip install ruff
Uso básico:
# Analizar un archivo
ruff check app.py
# Analizar un directorio completo
ruff check src/
# Mostrar errores con contexto
ruff check app.py --show-source
# Corregir errores automáticamente donde sea posible
ruff check app.py --fix
Lo que detecta relevante para hallucinations:
F821: Undefined name → Variable o función no definida
F401: Imported but unused → Import que se importa pero no se usa
E902: Syntax error → Error de sintaxis
F811: Redefined unused name → Redefinición de variable
Ejemplo:
# archivo: app.py
from fastapi.security import OAuth2TokenValidator # ← ¿Hallucination?
from pydantic import BaseModel
class User(BaseModel):
name: str
$ ruff check app.py
app.py:1:1: F401 `fastapi.security.OAuth2TokenValidator` imported but unused
Espera — ruff lo marca como "imported but unused", no como "import no existente." Esto es porque ruff analiza texto, no ejecuta imports. Para verificar que el import existe, necesitas la capa 2 o ejecutar Python directamente.
Limitación importante: ruff detecta imports no usados pero no verifica que el import exista. Un import falso que se usa en el código no será marcado por ruff. Necesitas type checkers (capa 2) para eso.
flake8 con plugins
Si prefieres flake8 o tu proyecto ya lo usa:
pip install flake8 flake8-import-order flake8-bugbear
# Analizar un archivo
flake8 app.py
# Con más detalle
flake8 app.py --show-source --statistics
Configuración recomendada de ruff
Crea un archivo ruff.toml en la raíz de tu proyecto:
# ruff.toml
line-length = 88
target-version = "py311"
[lint]
select = [
"E", # pycodestyle errors
"F", # pyflakes (imports, undefined names)
"I", # isort (import ordering)
"N", # pep8 naming
"UP", # pyupgrade (deprecated syntax)
"B", # bugbear (common bugs)
"S", # bandit (security issues)
"T20", # print statements
"SIM", # simplify
]
[lint.per-file-ignores]
"tests/*" = ["S101"] # Allow assert in tests
Lo que ruff atrapa y lo que no
| Hallucination | ¿ruff lo detecta? | Notas |
|---|---|---|
| Import no existente (no usado) | ✅ F401 | Marca como unused |
| Import no existente (usado) | ❌ | Necesitas type checker o python -c |
| Parámetro incorrecto | ❌ | No analiza runtime |
| Lógica incorrecta | ❌ | No entiende semántica |
| Security issues básicos | ✅ S | Con bandit rules habilitado |
| Variable no definida | ✅ F821 | |
| Código deprecated | ✅ UP |
Capa 2: Type Checkers
mypy — verificación de tipos estática
mypy va más allá de ruff: verifica que los tipos de datos sean consistentes, que los atributos y métodos que llamas existan en los tipos declarados, y que las signatures de funciones sean correctas.
Instalación:
pip install mypy
Uso básico:
# Verificar un archivo
mypy app.py
# Verificar con más estrictez
mypy app.py --strict
# Ignorar imports sin stubs
mypy app.py --ignore-missing-imports
Lo que detecta relevante para hallucinations:
# archivo: app.py
from fastapi.security import OAuth2TokenValidator # ← Hallucination
from pydantic import BaseModel, Field
class User(BaseModel):
email: str = Field(..., unique=True) # ← unique no es parámetro de Field
$ mypy app.py
app.py:1: error: Module "fastapi.security" has no attribute "OAuth2TokenValidator"
app.py:5: error: Unexpected keyword argument "unique" for "Field"
mypy detecta ambas hallucinations: el import falso Y el parámetro incorrecto. Esto lo hace significativamente más útil que ruff para detectar hallucinations.
pyright — alternativa más rápida
Si usas VS Code, pyright (el engine detrás de Pylance) es otra opción excelente:
pip install pyright
# Verificar un archivo
pyright app.py
# Verificar con output detallado
pyright app.py --outputjson
Configuración de mypy para detección de hallucinations
Crea mypy.ini en la raíz del proyecto:
[mypy]
python_version = 3.11
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
check_untyped_defs = True
warn_unused_ignores = True
show_error_codes = True
# Para librerías sin type stubs
[mypy-uvicorn.*]
ignore_missing_imports = True
Lo que mypy atrapa y lo que no
| Hallucination | ¿mypy lo detecta? | Notas |
|---|---|---|
| Import no existente | ✅ | "has no attribute X" |
| Parámetro incorrecto (con types) | ✅ | "Unexpected keyword argument" |
| Parámetro incorrecto (con **kwargs) | ❌ | **kwargs acepta cualquier nombre |
| Método no existente | ✅ | "has no attribute X" |
| Valor de parámetro incorrecto | ❌ | No verifica valores, solo tipos |
| Lógica incorrecta | ❌ | No entiende semántica |
| Tipo de retorno incorrecto | ✅ | Si los tipos están anotados |
Ejemplo completo de mypy atrapando hallucinations
# archivo: auth_service.py
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2TokenValidator # ← H1
from pydantic import BaseModel, Field
import jwt
app = FastAPI()
class UserCreate(BaseModel):
email: str = Field(..., unique=True) # ← H2
name: str
def create_token(data: dict) -> str:
return jwt.encode(data, "secret", algorithm="HS256")
def verify_token(token: str) -> dict:
return jwt.decode(token, "secret", algorithms=["HS256"], verify=True) # ← H3
$ mypy auth_service.py
auth_service.py:2: error: Module "fastapi.security" has no attribute
"OAuth2TokenValidator" [attr-defined]
auth_service.py:6: error: Unexpected keyword argument "unique" for
"Field" [call-arg]
auth_service.py:14: error: Unexpected keyword argument "verify" for
"decode" [call-arg]
Found 3 errors in 1 file (checked 1 source file)
mypy detectó las 3 hallucinations. Esto es por lo que la capa 2 es tan valiosa: una sola ejecución puede encontrar múltiples hallucinations que tu ojo podría pasar por alto.
Capa 3: Quick Tests
Por qué los tests son esenciales contra hallucinations de lógica
Las capas 1 y 2 no detectan hallucinations de lógica — código que compila, tiene tipos correctos, pero produce resultados incorrectos. Para eso necesitas ejecutar el código y verificar el output.
El proceso de 3 minutos
Para cualquier función sospechosa, escribe un quick test en 3 minutos o menos:
# Método 1: python -c (para verificaciones rápidas de una línea)
python -c "
from app import validate_email
tests = [
('user@domain.com', True),
('@domain.com', False),
('user@.com', False),
('', False),
]
for email, expected in tests:
result = validate_email(email)
status = '✅' if result == expected else '❌'
print(f'{status} validate_email(\"{email}\") = {result}, expected {expected}')
"
# Método 2: pytest con un archivo temporal
cat > test_quick.py << 'EOF'
from app import calculate_median
import statistics
def test_median_odd():
assert calculate_median([1, 2, 3]) == statistics.median([1, 2, 3])
def test_median_even():
assert calculate_median([1, 2, 3, 4]) == statistics.median([1, 2, 3, 4])
def test_median_single():
assert calculate_median([5]) == 5.0
def test_median_empty():
import pytest
with pytest.raises(ValueError):
calculate_median([])
EOF
pytest test_quick.py -v
Plantillas de quick tests por tipo de función
Para funciones de validación:
def test_validation_function(validate_func):
"""Template for testing validation functions."""
valid_inputs = [
"normal_valid_input",
]
invalid_inputs = [
"", # vacío
None, # null
" ", # solo espacios
"a" * 10000, # muy largo
]
for inp in valid_inputs:
assert validate_func(inp) == True, f"Should accept: {inp}"
for inp in invalid_inputs:
assert validate_func(inp) == False, f"Should reject: {inp}"
Para funciones de cálculo:
import math
def test_calculation_function(calc_func, reference_func):
"""Template for testing calculation functions against a reference."""
test_cases = [
[1, 2, 3, 4, 5], # normal
[1], # un elemento
[0, 0, 0], # todos cero
[-1, -2, -3], # negativos
[1.5, 2.7, 3.14], # decimales
list(range(1000)), # grande
]
for data in test_cases:
result = calc_func(data)
expected = reference_func(data)
assert math.isclose(result, expected, rel_tol=1e-9), \
f"For {data[:5]}...: got {result}, expected {expected}"
Para funciones de transformación:
def test_transformation_function(transform_func):
"""Template for testing data transformation functions."""
assert transform_func("hello") is not None # no retorna None
original = "test_input"
result = transform_func(original)
assert isinstance(result, str) # tipo correcto
assert transform_func("") == "" # vacío produce vacío
Verificar imports con python -c
Para la capa más básica de verificación, ejecuta los imports directamente:
# Verificar un import específico
python -c "from fastapi.security import OAuth2TokenValidator" 2>&1
# ImportError: cannot import name 'OAuth2TokenValidator'...
# Verificar múltiples imports de un archivo
python -c "
imports_to_check = [
('fastapi.security', 'OAuth2PasswordBearer'),
('fastapi.security', 'OAuth2TokenValidator'),
('pydantic', 'BaseModel'),
('pydantic', 'EmailStr'),
]
for module, name in imports_to_check:
try:
exec(f'from {module} import {name}')
print(f' ✅ from {module} import {name}')
except ImportError as e:
print(f' ❌ from {module} import {name} — {e}')
"
Verificar signatures con inspect
# Ver la signature de una función
python -c "
import inspect
import jwt
sig = inspect.signature(jwt.decode)
print(f'jwt.decode{sig}')
print()
for name, param in sig.parameters.items():
print(f' {name}: {param.kind.name} = {param.default}')
"
Output:
jwt.decode(jwt, key='', algorithms=None, options=None, ...)
jwt: POSITIONAL_OR_KEYWORD = <class 'inspect._empty'>
key: POSITIONAL_OR_KEYWORD =
algorithms: POSITIONAL_OR_KEYWORD = None
options: POSITIONAL_OR_KEYWORD = None
...
Si el código usa jwt.decode(..., verify=True) y verify no aparece en la signature, es una hallucination confirmada.
Capa 4: Documentación Oficial
Cuándo usar la documentación
Usa la documentación cuando:
- Las capas 1-3 no dan una respuesta clara
- El parámetro podría ser aceptado via
**kwargs(no aparece en la signature pero podría funcionar) - Necesitas verificar valores válidos para un parámetro (no solo que el parámetro exista)
- El comportamiento depende de la versión de la librería
Cómo verificar eficientemente
Para verificar un parámetro:
1. Abre la documentación oficial de la librería
2. Busca la función/clase específica
3. Verifica:
a. ¿El parámetro existe?
b. ¿El tipo es correcto?
c. ¿El valor es válido?
d. ¿Está deprecated?
Ejemplo: verificar pd.DataFrame.to_json(orient="dict")
- Abre: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_json.html
- Busca el parámetro
orient - Valores válidos:
"split","records","index","columns","values","table" "dict"no está en la lista → hallucination confirmada
Ejemplo: verificar requests.get(url, verify_ssl=True)
- Abre: https://requests.readthedocs.io/en/latest/api/#requests.get
- Busca parámetros aceptados
- El parámetro de verificación SSL es
verify, noverify_ssl - Hallucination confirmada
Documentación offline con help()
Si no tienes acceso a internet o prefieres verificar rápido:
# Ver documentación completa
python -c "import requests; help(requests.get)"
# Ver solo la primera parte (signature y descripción)
python -c "
import requests
doc = requests.get.__doc__
print(doc[:500] if doc else 'No documentation')
"
# Ver los parámetros de un método
python -c "
import pandas as pd
help(pd.DataFrame.to_json)
" | head -30
Links de documentación esenciales
Para el stack FastAPI que usarás en el proyecto integrador:
| Librería | Documentación | Qué verificar |
|---|---|---|
| FastAPI | https://fastapi.tiangolo.com/reference/ | Clases de security, parámetros de decoradores |
| Pydantic | https://docs.pydantic.dev/latest/ | Field(), validators, model_config |
| SQLAlchemy | https://docs.sqlalchemy.org/ | Query API, Column types, relationships |
| PyJWT / python-jose | https://pyjwt.readthedocs.io/ | encode/decode signatures |
| requests | https://requests.readthedocs.io/ | Parámetros de get/post/put/delete |
| pandas | https://pandas.pydata.org/docs/ | Métodos de DataFrame, parámetros |
El Proceso Completo: De Código a Confianza
Workflow de verificación en 4 pasos
Cuando Claude Code genera código, aplica este proceso:
PASO 1: ruff check (< 1 segundo)
├── Ejecuta: ruff check archivo.py
├── Busca: F401 (unused imports), F821 (undefined), E902 (syntax)
├── Si encuentra errores → Corrige antes de continuar
└── Si pasa → Continúa a paso 2
PASO 2: mypy (2-5 segundos)
├── Ejecuta: mypy archivo.py --ignore-missing-imports
├── Busca: attr-defined, call-arg, type errors
├── Si encuentra errores → Investiga cada uno
│ ├── attr-defined → Import falso o método no existente
│ ├── call-arg → Parámetro incorrecto
│ └── type error → Tipo incorrecto
└── Si pasa → Continúa a paso 3
PASO 3: Quick tests (1-5 minutos)
├── Identifica funciones de alto riesgo:
│ ├── Validación
│ ├── Cálculos
│ ├── Seguridad
│ └── Transformación de datos
├── Escribe 3-5 tests rápidos para cada una
├── Ejecuta: pytest test_quick.py -v
├── Si algún test falla → Hallucination de lógica encontrada
└── Si todos pasan → Confianza razonable
PASO 4: Documentación (2-10 minutos, solo si necesario)
├── Para parámetros sospechosos que pasaron mypy (**kwargs)
├── Para valores de parámetros (mypy no verifica valores)
├── Para comportamiento version-specific
└── Verifica contra documentación oficial
Ejemplo completo: verificando código generado por AI
Claude Code genera este código:
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel, Field
import jwt
import requests
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=200)
price: float = Field(..., gt=0)
category: str = Field(..., unique=True)
def verify_token(token: str) -> dict:
return jwt.decode(token, "secret", algorithms=["HS256"], verify=True)
def fetch_external_data(url: str) -> dict:
response = requests.get(url, verify_ssl=True, timeout=30)
return response.json()
PASO 1: ruff check
$ ruff check app.py
# (suponiendo que todos los imports se usan) → Sin errores
PASO 2: mypy
$ mypy app.py
app.py:13: error: Unexpected keyword argument "unique" for "Field" [call-arg]
app.py:16: error: Unexpected keyword argument "verify" for "decode" [call-arg]
Resultados:
- ❌
Field(..., unique=True)—uniqueno es parámetro de Pydantic Field - ❌
jwt.decode(..., verify=True)—verifyno es parámetro de jwt.decode
PASO 3: Quick test (para verificar verify_ssl)
mypy no detectó verify_ssl porque requests usa **kwargs. Verificación manual:
python -c "
import inspect
import requests
sig = inspect.signature(requests.get)
print(sig)
"
# (url, **kwargs) — no podemos ver los parámetros reales
python -c "
import requests
help(requests.get)
" | grep -i "verify"
# :param verify: ... Either a boolean, in which case...
# El parámetro es "verify", no "verify_ssl"
Resultado:
- ❌
requests.get(url, verify_ssl=True)— el parámetro esverify, noverify_ssl
PASO 4: Documentación (confirmación)
- Abre https://requests.readthedocs.io/en/latest/api/
- Confirma:
verifyes el parámetro correcto
Resumen: 3 hallucinations detectadas en un archivo de 20 líneas. Tiempo total: ~5 minutos.
Automatización: Integrar en tu Workflow
Pre-commit hooks
Configura verificación automática antes de cada commit:
pip install pre-commit
Crea .pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.9.0
hooks:
- id: mypy
additional_dependencies:
- fastapi
- pydantic
- types-requests
pre-commit install
Ahora, cada vez que hagas git commit, ruff y mypy se ejecutan automáticamente. Si detectan hallucinations, el commit falla y puedes corregir antes de subir código.
Script de verificación rápida
Crea un script que ejecute las 3 primeras capas con un solo comando:
#!/bin/bash
# verify.sh — Verificación rápida de código AI-generated
FILE=${1:-"app.py"}
echo "=== Capa 1: ruff ==="
ruff check "$FILE" --show-source
echo ""
echo "=== Capa 2: mypy ==="
mypy "$FILE" --ignore-missing-imports --show-error-codes
echo ""
echo "=== Capa 3: Import verification ==="
python -c "
import ast
import sys
with open('$FILE') as f:
tree = ast.parse(f.read())
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
module = node.module or ''
for alias in node.names:
name = alias.name
try:
exec(f'from {module} import {name}')
print(f' ✅ from {module} import {name}')
except ImportError as e:
print(f' ❌ from {module} import {name} — {e}')
elif isinstance(node, ast.Import):
for alias in node.names:
try:
__import__(alias.name)
print(f' ✅ import {alias.name}')
except ImportError as e:
print(f' ❌ import {alias.name} — {e}')
"
echo ""
echo "=== Verificación completa ==="
chmod +x verify.sh
./verify.sh app.py
VS Code / Cursor integración
Si usas Cursor (como en esta guía), configura estas extensiones:
- Pylance (pyright built-in) — type checking en tiempo real
- Ruff — linting en tiempo real
- Python — IntelliSense y autocompletado
Con Pylance habilitado, verás errores de type checking directamente en el editor mientras Claude Code genera código. Si ves líneas subrayadas en rojo después de que Claude Code termina, revísalas — pueden ser hallucinations.
Conexión con Proyecto
Toolkit para el proyecto integrador
En el módulo 8, recibirás un codebase FastAPI con ~15-20 problemas. Tu proceso de verificación debería ser:
1. ruff check src/ → Encuentra imports no usados, syntax issues
2. mypy src/ → Encuentra imports falsos, parámetros incorrectos
3. Quick tests para funciones de seguridad → Encuentra lógica fabricada
4. Documentación para parámetros sospechosos → Confirma hallucinations
Estimación de tiempo:
- Capa 1 (ruff): 30 segundos
- Capa 2 (mypy): 2 minutos
- Capa 3 (quick tests): 15-20 minutos
- Capa 4 (docs): 5-10 minutos según necesidad
Total: ~30 minutos para la fase de detección de hallucinations del proyecto.
Qué te faltaría sin herramientas
Sin las capas automatizadas, la única herramienta sería tu ojo. Un developer promedio revisando 500-800 líneas de código detectaría:
- ~80% de imports falsos (los más obvios)
- ~50% de parámetros incorrectos (los que conoce)
- ~30% de lógica fabricada (solo en dominios que domina)
Con las 4 capas:
- ~95% de imports falsos (ruff + mypy + import verification)
- ~80% de parámetros incorrectos (mypy + docs)
- ~60% de lógica fabricada (quick tests + dominio knowledge)
Las herramientas no reemplazan tu ojo — lo complementan. Juntos cubren significativamente más que cualquiera por separado.
Troubleshooting
Problema 1: "mypy da demasiados errores en mi código — no sé cuáles son hallucinations"
Causa: mypy en modo strict puede dar cientos de errores en código que no tiene type annotations.
Solución: No uses --strict para la detección de hallucinations. Usa el modo default y enfócate en estos error codes:
[attr-defined] → Import falso o método no existente (ALTA prioridad)
[call-arg] → Parámetro incorrecto (ALTA prioridad)
[import] → Módulo no encontrado (ALTA prioridad)
[name-defined] → Variable no definida (MEDIA prioridad)
Ignora errores de tipos genéricos [type-arg], [return-type] — son issues de typing, no hallucinations.
Problema 2: "El import pasa python -c pero mypy dice que no existe"
Causa: El paquete está instalado pero no tiene type stubs.
Solución: Instala type stubs para las librerías principales:
pip install types-requests types-PyYAML types-redis
Para librerías sin stubs oficiales, agrega a mypy.ini:
[mypy-nombre_libreria.*]
ignore_missing_imports = True
Problema 3: "Las herramientas no detectan hallucinations en funciones con **kwargs"
Causa: **kwargs acepta cualquier argumento, ocultando hallucinations.
Solución: Para funciones con **kwargs (como requests.get()):
- No confíes en mypy para estas funciones
- Usa
inspect.signature()para ver los parámetros documentados - Verifica contra la documentación oficial
- Haz un quick test para confirmar que el parámetro tiene efecto
Problema 4: "No tengo tiempo para ejecutar 4 capas en cada archivo"
Causa: La verificación completa toma tiempo.
Solución: Prioriza:
- Siempre: Capa 1 (ruff) — toma <1 segundo
- Siempre: Capa 2 (mypy) — toma 2-5 segundos
- Para código de riesgo: Capa 3 (quick tests) — toma 1-5 minutos
- Solo si hay duda: Capa 4 (documentación) — toma 2-10 minutos
Las capas 1 y 2 deben ser automáticas (pre-commit hooks o extensiones de editor). Las capas 3 y 4 las usas selectivamente para código de alto riesgo.
Problema 5: "¿Estas herramientas funcionan para JavaScript/TypeScript?"
Causa: Esta guía usa Python, pero los principios aplican a cualquier lenguaje.
Solución: Los equivalentes en JS/TS:
- Capa 1: ESLint (linting)
- Capa 2: TypeScript compiler (type checking)
- Capa 3: Jest/Vitest (quick tests)
- Capa 4: MDN / documentación de npm packages
Los principios de las 4 capas son universales.
Ejercicios
Ejercicio 1: Ejecutar las 4 capas (Fácil)
Toma este código y ejecuta las 4 capas de verificación. Documenta qué encuentra cada capa:
from fastapi import FastAPI, HTTPException
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel, Field
import jwt
app = FastAPI()
class UserLogin(BaseModel):
email: str = Field(..., format="email")
password: str = Field(..., min_length=8)
def create_token(user_id: str) -> str:
return jwt.encode(
{"sub": user_id},
"secret",
algorithm="HS256"
)
Ver solución
Capa 1 (ruff):
- F401:
OAuth2PasswordBearerimported but unused - No otros errores
Capa 2 (mypy):
Field(..., format="email")—formatno es un parámetro de PydanticField(). Es una hallucination Tipo 3. mypy reporta:Unexpected keyword argument "format" for "Field".
Capa 3 (quick test):
- No hay funciones de lógica compleja para testear.
create_tokenes una llamada directa ajwt.encodecon parámetros simples.
Capa 4 (documentación):
- Confirma que
Field()no aceptaformat. Para validar email, Pydantic tieneEmailStr:email: EmailStr. jwt.encodeconalgorithm="HS256"es correcto.
Hallucinations encontradas: 1
Field(..., format="email")→formatno es parámetro de Field. Lo correcto es usarEmailStrcomo tipo:email: EmailStr.
Ejercicio 2: Configurar verificación (Medio)
Configura ruff + mypy para un proyecto Python. Crea los archivos de configuración y ejecuta contra un archivo de ejemplo. Documenta:
- Los archivos de configuración que creaste
- Los comandos que ejecutaste
- Los resultados
Ver solución
1. Archivos de configuración:
ruff.toml:
line-length = 88
target-version = "py311"
[lint]
select = ["E", "F", "I", "B", "S", "UP"]
mypy.ini:
[mypy]
python_version = 3.11
check_untyped_defs = True
show_error_codes = True
[mypy-uvicorn.*]
ignore_missing_imports = True
2. Comandos:
pip install ruff mypy
ruff check app.py --show-source
mypy app.py --show-error-codes
3. Resultados típicos:
- ruff reporta imports no usados, posibles bugs
- mypy reporta atributos no existentes, parámetros incorrectos
La configuración exacta depende de tu proyecto. Lo importante es tener ambas herramientas configuradas y corriendo.
Ejercicio 3: Script de verificación de imports (Medio)
Escribe un script que reciba un archivo Python y verifique automáticamente todos los imports. El script debe:
- Extraer todos los imports del archivo
- Intentar ejecutar cada import
- Reportar cuáles funcionan y cuáles no
Ver solución
import ast
import sys
import importlib
def verify_imports(filepath: str) -> None:
"""Verifies all imports in a Python file."""
with open(filepath) as f:
tree = ast.parse(f.read())
print(f"Verifying imports in {filepath}:\n")
passed = 0
failed = 0
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
module = node.module or ""
for alias in node.names:
name = alias.name
try:
exec(f"from {module} import {name}")
print(f" ✅ from {module} import {name}")
passed += 1
except ImportError as e:
print(f" ❌ from {module} import {name} — {e}")
failed += 1
elif isinstance(node, ast.Import):
for alias in node.names:
try:
importlib.import_module(alias.name)
print(f" ✅ import {alias.name}")
passed += 1
except ImportError as e:
print(f" ❌ import {alias.name} — {e}")
failed += 1
print(f"\nResults: {passed} passed, {failed} failed")
if failed > 0:
print(f"⚠️ {failed} potential hallucination(s) found!")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python verify_imports.py <file.py>")
sys.exit(1)
verify_imports(sys.argv[1])
Uso: python verify_imports.py app.py
Ejercicio 4: Detectar con mypy (Difícil)
Este código tiene 3 hallucinations. Usa mypy para encontrar al menos 2 de las 3:
from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import Session, declarative_base, joinedload
from typing import Optional, List
import jwt
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allowed_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
Base = declarative_base()
class UserDB(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True)
name = Column(String)
class UserCreate(BaseModel):
email: str = Field(..., unique=True)
name: str = Field(..., min_length=2, max_length=100)
class UserResponse(BaseModel):
id: int
email: str
name: str
model_config = {"from_attributes": True}
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def get_current_user(token: str = Depends(oauth2_scheme)):
payload = jwt.decode(token, "secret", algorithms=["HS256"], verify=True)
return payload.get("sub")
Ver solución
Las 3 hallucinations:
-
allowed_origins=["*"](línea 14) — El parámetro correcto esallow_origins, noallowed_origins. mypy puede no detectar esto porqueadd_middlewarepuede aceptar**kwargs. Este es un caso donde la Capa 4 (documentación) es necesaria. -
Field(..., unique=True)(línea 29) —uniqueno es un parámetro de Pydantic Field. mypy sí detecta esto:Unexpected keyword argument "unique" for "Field" [call-arg]. -
jwt.decode(..., verify=True)(línea 42) —verifyno es un parámetro dejwt.decode. mypy sí detecta esto:Unexpected keyword argument "verify" for "decode" [call-arg].
Resultado: mypy detecta 2 de las 3 hallucinations. La tercera (allowed_origins) requiere verificación manual o documentación porque CORSMiddleware acepta **kwargs.
Lección: mypy es poderoso pero no omnisciente. Las funciones con **kwargs son un punto ciego. Para esas, necesitas la capa 4 (documentación) o quick tests.
Ejercicio 5: Diseñar tu pipeline de verificación (Difícil)
Diseña un pipeline de verificación personalizado para tu stack de trabajo. Debe incluir:
- Herramientas de cada capa
- Configuración mínima
- Comandos para ejecutar
- Qué tipo de hallucination detecta cada paso
- Cuándo se ejecuta (manual vs automático)
Ver solución (ejemplo con FastAPI + PostgreSQL + pytest)
Pipeline de verificación:
| Capa | Herramienta | Comando | Detecta | Ejecución |
|---|---|---|---|---|
| 1 | ruff | ruff check src/ | Imports no usados, syntax | Automático (pre-commit) |
| 1b | ruff format | ruff format src/ | Formateo | Automático (pre-commit) |
| 2 | mypy | mypy src/ --show-error-codes | Imports falsos, parámetros incorrectos | Automático (pre-commit) |
| 3a | Import check | python verify_imports.py | Imports que no existen | Manual (post-generación) |
| 3b | pytest | pytest tests/ -v --tb=short | Lógica incorrecta | Manual (post-generación) |
| 4 | Docs check | Verificar contra docs oficiales | Todo lo que 1-3 no atrapa | Manual (cuando hay duda) |
Configuración mínima:
ruff.tomlcon rules E, F, I, B, S, UPmypy.inicon check_untyped_defs = True.pre-commit-config.yamlcon ruff + mypy hooks- Script
verify_imports.pyen la raíz del proyecto
Estimación de tiempo por ejecución:
- Capas 1-2 (automático): 0 segundos (se ejecutan en pre-commit)
- Capa 3a (import check): 30 segundos
- Capa 3b (tests): 1-5 minutos
- Capa 4 (docs): 5-10 minutos (solo si necesario)
Resumen
En esta cápsula aprendiste:
- Las 4 capas de verificación: linters → type checkers → quick tests → documentación
- Capa 1 (ruff): detecta imports no usados, syntax errors — automática, < 1 segundo
- Capa 2 (mypy): detecta imports falsos, parámetros incorrectos — automática, 2-5 segundos
- Capa 3 (tests): detecta lógica fabricada — manual, 1-5 minutos
- Capa 4 (docs): verifica todo lo que las otras no cubren — manual, 2-10 minutos
- mypy es la herramienta más valiosa contra hallucinations de tipo 1-3
- Las funciones con
**kwargsson un punto ciego de todas las herramientas automatizadas - La automatización (pre-commit hooks, extensiones de editor) convierte la verificación en parte de tu flujo natural
Próxima cápsula: Ejercicio: Detectar Hallucinations — 5 snippets con hallucinations ocultas. Es hora de poner a prueba todo lo que aprendiste.
Recursos Adicionales
- ruff — Python Linter - Documentación oficial del linter más rápido de Python
- mypy — Type Checker - Documentación oficial del type checker
- pyright - Type checker de Microsoft, engine de Pylance
- pre-commit - Framework para git hooks automatizados
- pytest — Testing Framework - Documentación oficial de pytest
- Python inspect module - Inspeccionar signatures y metadata de funciones
Debugging & Code Review with Claude Code — Módulo 3, Cápsula 05 Claude Code Agentic Development Path — Guía #6 de 11