Módulo 4: Chain-of-Thought y Razonamiento

7. Limitaciones y Anti-Patterns de CoT

Descripción

CoT no es una bala de plata. Hay situaciones donde CoT no solo no ayuda, sino que activamente empeora los resultados. Esta cápsula cubre: cuándo CoT es contraproducente, el fenómeno del "razonamiento fabricado" (razonamiento persuasivo pero falso), alucinaciones específicas al reasoning, y los anti-patterns más comunes que debes evitar en producción.

Tiempo estimado: 60-75 minutos


Limitación 1: Tareas Donde CoT No Ayuda (o Empeora)

Tareas Creativas

CoT rigidiza el pensamiento creativo. Al pedir al modelo que "razone paso a paso" en una tarea creativa, le impones una estructura lineal que mata la serendipia.

from openai import OpenAI

client = OpenAI()

def comparar_creativo_con_sin_cot(tarea_creativa: str) -> dict:
    """
    Demuestra cómo CoT puede dañar la calidad creativa.
    """
    # Sin CoT
    r_sin = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": tarea_creativa}],
        temperature=0.8,  # Alta temperatura para creatividad
        max_tokens=200
    )
    
    # Con CoT (probablemente peor para creatividad)
    r_con = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{tarea_creativa}\n\nPiensa paso a paso."}],
        temperature=0.8,
        max_tokens=400
    )
    
    return {
        "sin_cot": r_sin.choices[0].message.content,
        "con_cot": r_con.choices[0].message.content
    }


# Ejemplos donde CoT daña
TAREAS_CREATIVAS = [
    "Escribe un haiku sobre el invierno",
    "Genera 5 nombres creativos para una startup de inteligencia artificial",
    "Inventa una metáfora original para describir la ansiedad",
    "Escribe el primer párrafo de una novela noir ambientada en el futuro",
]

if __name__ == "__main__":
    for tarea in TAREAS_CREATIVAS[:2]:
        print(f"\n=== {tarea} ===")
        resultado = comparar_creativo_con_sin_cot(tarea)
        print(f"\nSIN CoT:\n{resultado['sin_cot']}")
        print(f"\nCON CoT:\n{resultado['con_cot'][:200]}...")
        print("\nObservación: La versión con CoT tiende a ser más mecánica y menos imaginativa")

¿Por qué pasa esto? CoT activa el modo "análisis sistemático" del modelo. La creatividad requiere conexiones no lineales entre conceptos, y el formato "paso a paso" inhibe esas conexiones.

Tareas de Opinión o Preferencia Subjetiva

TAREAS_SUBJETIVAS = [
    "¿Es mejor React o Vue para un proyecto web?",
    "¿Cuál es el mejor libro sobre programación?",
    "¿Debo usar PostgreSQL o MongoDB para mi app?",
]

# CoT para estas preguntas genera una ilusión de objetividad
# El modelo razona como si hubiera una respuesta "correcta" cuando no la hay
# Esto puede crear un falso sentido de certeza en el usuario

Clasificación Simple con Criterios Claros

# ❌ Usar CoT para esto es un desperdicio de tokens
prompt_cot_innecesario = """
Clasifica si el siguiente número es par o impar: 42

Piensa paso a paso:
"""
# Respuesta: "Paso 1: El número es 42. Paso 2: Los números pares son divisibles por 2. 
# Paso 3: 42 / 2 = 21, sin residuo. Paso 4: Por lo tanto, 42 es par."
# → 50 tokens extras para algo que puede responderse en 3 tokens

# ✅ Sin CoT para tareas simples
prompt_directo = "¿Es 42 par o impar? Responde una palabra."

Preguntas Factuales Directas

PREGUNTAS_FACTUALES = [
    "¿Capital de Francia?",
    "¿Cuánto es 2 + 2?",
    "¿Qué lenguaje de programación creó Guido van Rossum?",
]

# CoT añade tokens sin valor: "Francia es un país europeo... su capital histórica... 
# por lo tanto: París"
# La respuesta directa "París" es igualmente correcta y 5x más barata

Limitación 2: CoT Fabricado (Fabricated Reasoning)

El fenómeno más peligroso: el modelo genera razonamiento que suena correcto, parece correcto, pero llega a una conclusión incorrecta.

Tipos de Razonamiento Fabricado

Tipo 1: Error de Cálculo Disfrazado

# El modelo muestra "trabajo" pero comete errores internos

RAZONAMIENTO_INCORRECTO = """
Problema: ¿Cuánto es 23 × 47?

Razonamiento:
Descomponemos: 23 × 47 = 23 × 40 + 23 × 7
23 × 40 = 920  ✓ (correcto)
23 × 7 = 151   ✗ (incorrecto, debería ser 161)
920 + 151 = 1071  ✗ (respuesta final incorrecta, correcta es 1081)
"""

# El problema: el razonamiento "parece" correcto (tiene pasos, tiene operaciones)
# pero tiene un error en el paso 2 que se propaga

def detectar_errores_matematicos(razonamiento: str) -> list[str]:
    """
    Analiza un razonamiento CoT y detecta errores matemáticos.
    Extrae todas las operaciones y verifica cada una.
    """
    import re
    errores = []
    
    # Buscar multiplicaciones
    for match in re.finditer(r'(\d+)\s*[×x\*]\s*(\d+)\s*=\s*(\d+)', razonamiento):
        a, b, resultado = int(match.group(1)), int(match.group(2)), int(match.group(3))
        correcto = a * b
        if correcto != resultado:
            errores.append(f"Error: {a} × {b} = {resultado} (correcto: {correcto})")
    
    # Buscar sumas
    for match in re.finditer(r'(\d+)\s*\+\s*(\d+)\s*=\s*(\d+)', razonamiento):
        a, b, resultado = int(match.group(1)), int(match.group(2)), int(match.group(3))
        correcto = a + b
        if correcto != resultado:
            errores.append(f"Error: {a} + {b} = {resultado} (correcto: {correcto})")
    
    return errores


errores = detectar_errores_matematicos(RAZONAMIENTO_INCORRECTO)
print(f"Errores encontrados: {errores}")
# Output: ['Error: 23 × 7 = 151 (correcto: 161)']

Tipo 2: Salto Lógico No Justificado

LOGICA_FABRICADA = """
Problema: ¿Todos los cisnes son blancos?

Razonamiento:
- Los cisnes que he visto son blancos.
- Los libros de zoología muestran cisnes blancos.
- La asociación cultural de "cisne" implica blancura.
- Los cisnes blancos son los más comunes.
- Por lo tanto, todos los cisnes son blancos. ✓

RESPUESTA: Sí, todos los cisnes son blancos.
"""

# El error: los cisnes negros (Cygnus atratus) existen en Australia
# El razonamiento usa evidencia anecdótica y generaliza incorrectamente
# Esto es un salto inductivo inválido (el problema de inducción de Hume)

Tipo 3: Premisa Inventada

PREMISA_INVENTADA = """
Problema: ¿Cuánto tiempo tarda en hervir el agua a 2000 metros de altitud?

Razonamiento:
- Al nivel del mar, el agua hierve a 100°C.
- A mayor altitud, la presión atmosférica disminuye.
- A 2000 metros, el agua hierve a 93.4°C exactamente.  ← PREMISA INVENTADA
- Con menos temperatura de ebullición, el proceso es más rápido.
- Por lo tanto, el agua hierve en aproximadamente 8 minutos.  ← CONCLUSIÓN INCORRECTA
"""

# El modelo inventó "93.4°C exactamente" sin evidencia
# La temperatura real a 2000m es ~93.3°C, pero más importante:
# "hervirá más rápido" es incorrecto; tarda igual o más porque la transferencia 
# de calor en agua es similar

Detección de Razonamiento Fabricado

def evaluar_razonamiento(problema: str, razonamiento_propuesto: str) -> dict:
    """
    Usa un segundo LLM para evaluar si el razonamiento es válido.
    
    Esta función implementa el patrón de "auditor externo" para
    detectar razonamiento fabricado.
    """
    prompt_evaluacion = f"""Eres un auditor crítico de razonamientos. Tu trabajo es encontrar fallos,
no confirmar que todo está bien. Sé específico sobre cualquier problema.

PROBLEMA: {problema}

RAZONAMIENTO PROPUESTO:
{razonamiento_propuesto}

Analiza paso a paso:
1. ¿Cada operación matemática es correcta? (verifica manualmente)
2. ¿Hay saltos lógicos no justificados?
3. ¿Se asumen hechos no dados en el problema?
4. ¿La conclusión sigue necesariamente de las premisas?

VEREDICTO: VÁLIDO / INVÁLIDO / PARCIALMENTE_VÁLIDO
CONFIANZA: Alta/Media/Baja
ERRORES ENCONTRADOS: [lista específica o "Ninguno"]"""

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt_evaluacion}],
        temperature=0,
        max_tokens=500
    )
    output = response.choices[0].message.content
    
    import re
    veredicto_match = re.search(r'VEREDICTO:\s*(VÁLIDO|INVÁLIDO|PARCIALMENTE_VÁLIDO)', output)
    
    return {
        "evaluacion": output,
        "veredicto": veredicto_match.group(1) if veredicto_match else "INDETERMINADO"
    }

Limitación 3: Alucinaciones en Reasoning

Las alucinaciones en CoT son especialmente peligrosas porque vienen empaquetadas en razonamiento que parece sólido.

Tipos de Alucinación en CoT

TIPOS_ALUCINACION = {
    "datos_inventados": {
        "descripcion": "El modelo cita estadísticas, fechas o hechos que no existen o son incorrectos",
        "ejemplo": "Según el estudio de Harvard de 2019, el 73% de los proyectos fallan por...",
        "mitigacion": "Pedir 'solo usa información del contexto' o verificar externamente"
    },
    "citas_falsas": {
        "descripcion": "El modelo atribuye citas o afirmaciones a personas/papers que no las dijeron",
        "ejemplo": "Como dijo Einstein: 'La locura es hacer lo mismo esperando resultados diferentes'",
        "mitigacion": "Nunca asumir que una cita es real; verificar en fuentes primarias"
    },
    "premisas_no_proporcionadas": {
        "descripcion": "El modelo introduce información no dada en el problema",
        "ejemplo": "El envío tarda 3 días... (el problema no decía nada sobre envío)",
        "mitigacion": "Instrucción explícita: 'Solo usa los datos proporcionados en el problema'"
    },
    "generalizacion_excesiva": {
        "descripcion": "Extrapola patrones de casos específicos a casos generales",
        "ejemplo": "Como los últimos 5 años han sido positivos, el próximo también lo será",
        "mitigacion": "Pedir que identifique suposiciones explícitas"
    }
}


def prompt_anti_alucinacion(problema: str, contexto: str = "") -> str:
    """
    Template de prompt diseñado para minimizar alucinaciones en el reasoning.
    """
    instrucciones_anti_alucinacion = """
REGLAS ESTRICTAS:
- Solo usa datos explícitamente proporcionados en el problema o contexto
- Si necesitas información que NO está en el problema, di "DATO FALTANTE: [qué necesitas]"
- No cites estudios, estadísticas o hechos a menos que estén en el contexto
- Cuando hagas suposiciones, márcalas explícitamente como "SUPOSICIÓN: [qué asumes]"
- Si hay ambigüedad, identifícala antes de razonar: "AMBIGÜEDAD: [qué puede interpretarse de dos formas]"
"""
    
    prompt = f"""{instrucciones_anti_alucinacion}

{'Contexto disponible:' + chr(10) + contexto + chr(10) if contexto else ''}

Problema: {problema}

Razonamiento (siguiendo las reglas):"""
    
    return prompt

Anti-Patterns de CoT: Los 7 Más Comunes

Anti-Pattern 1: CoT para Todo (Overuse)

# ❌ MAL: CoT en cada llamada sin importar la tarea
class ChatbotMalo:
    def responder(self, pregunta: str) -> str:
        return self._llamar_con_cot(pregunta)  # Siempre CoT
    
    def _llamar_con_cot(self, pregunta: str) -> str:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{pregunta}\n\nPiensa paso a paso."}],
            temperature=0, max_tokens=500
        )
        return response.choices[0].message.content

# Resultado: 3-5x más tokens en preguntas que no lo necesitan
# "¿Cuál es el horario de atención?" → 200 tokens de reasoning para decir "9am-6pm"


# ✅ BIEN: CoT solo cuando agrega valor
class ChatbotInteligente:
    TAREAS_COT = ["calcul", "razon", "analiz", "compara", "evalúa", "optimiz", "decid"]
    TAREAS_NO_COT = ["cuál es", "qué hora", "dónde está", "cómo se llama", "capital de"]
    
    def responder(self, pregunta: str) -> str:
        if self._necesita_cot(pregunta):
            return self._llamar_con_cot(pregunta)
        else:
            return self._llamar_directo(pregunta)
    
    def _necesita_cot(self, pregunta: str) -> bool:
        pregunta_lower = pregunta.lower()
        # Positivo si hay palabras que sugieren razonamiento complejo
        tiene_cot = any(t in pregunta_lower for t in self.TAREAS_COT)
        # Negativo si claramente es factual/simple
        es_simple = any(t in pregunta_lower for t in self.TAREAS_NO_COT)
        return tiene_cot and not es_simple
    
    def _llamar_con_cot(self, pregunta: str) -> str:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{pregunta}\n\nPiensa paso a paso."}],
            temperature=0, max_tokens=500
        )
        return response.choices[0].message.content
    
    def _llamar_directo(self, pregunta: str) -> str:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": pregunta}],
            temperature=0, max_tokens=100
        )
        return response.choices[0].message.content

Anti-Pattern 2: Razonamiento Sin Verificación en Producción

# ❌ MAL: En producción, confiar ciegamente en CoT sin verificación
def calcular_descuento_MAL(precio: float, descuento_pct: float) -> float:
    """Sistema de descuentos que usa CoT sin verificación."""
    problema = f"Si un producto cuesta ${precio} y tiene {descuento_pct}% de descuento, ¿cuánto hay que pagar?"
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{problema}\n\nPiensa paso a paso."}],
        temperature=0, max_tokens=300
    )
    
    import re
    texto = response.choices[0].message.content
    match = re.search(r'\$?([\d.]+)', texto)
    return float(match.group(1)) if match else precio  # Retorna precio incorrecto si falla


# ✅ BIEN: Verificar con código determinístico para cálculos críticos
def calcular_descuento_BIEN(precio: float, descuento_pct: float) -> dict:
    """Sistema con verificación determinística para operaciones críticas."""
    # Cálculo determinístico (siempre correcto)
    precio_final_correcto = precio * (1 - descuento_pct / 100)
    
    # CoT para explicación al usuario (no para el cálculo)
    problema = f"Explica al cliente cómo calculamos su descuento: precio original ${precio}, descuento {descuento_pct}%, precio final ${precio_final_correcto:.2f}"
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": problema}],
        temperature=0, max_tokens=150
    )
    
    return {
        "precio_final": precio_final_correcto,  # NUNCA usar el número del LLM para esto
        "explicacion_usuario": response.choices[0].message.content
    }

Anti-Pattern 3: Confiar en Razonamiento Persuasivo

# El razonamiento persuasivo (que suena bien) ≠ razonamiento correcto

RAZONAMIENTO_PERSUASIVO_PERO_FALSO = """
Problema: Si lancé una moneda 9 veces y salió cara todas las veces,
¿qué probabilidad hay de que la décima vez salga cara?

Razonamiento:
- Las primeras 9 tiradas salieron cara, lo cual es extremadamente improbable (1/512)
- Esto sugiere que la moneda puede estar sesgada hacia cara
- Con un sesgo tan pronunciado, es más probable que la décima también sea cara
- La probabilidad de que salga cara es mayor al 50%

Respuesta: ~75% de probabilidad de cara

Por qué es falso:
- Una moneda justa tiene memoria: 0. Cada tirada es independiente.
- La probabilidad siempre es 50% si la moneda es justa.
- El razonamiento "la moneda puede estar sesgada" sería válido solo si 
  tienes evidencia de que la moneda no es justa.
- Esto se llama "Gambler's Fallacy" o "Falacia del Apostador"
"""


def detectar_falacias_logicas(argumento: str) -> dict:
    """
    Identifica falacias lógicas comunes en un razonamiento.
    """
    prompt = f"""Identifica si hay falacias lógicas en este razonamiento.

Razonamiento: {argumento}

Falacias a verificar:
- Falacia del apostador (asumir independencia de eventos como dependiente)
- Falacia de generalización apresurada (n=pequeño → conclusión general)
- Post hoc ergo propter hoc (correlación = causalidad)
- Pendiente resbaladiza (A → B → C sin justificación)
- Apelación a la autoridad sin evidencia
- Falacia de afirmación del consecuente

Lista las falacias encontradas con explicación, o "Sin falacias detectadas"."""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        max_tokens=400
    )
    
    output = response.choices[0].message.content
    tiene_falacias = "sin falacias" not in output.lower()
    
    return {
        "analisis": output,
        "tiene_falacias": tiene_falacias
    }

Anti-Pattern 4: CoT Demasiado Largo

# ❌ MAL: Sin límite de pasos, el modelo puede generar 20 párrafos para un cálculo simple
prompt_sin_limite = """
¿Cuánto es 15% de 280?

Piensa paso a paso.
"""
# El modelo puede explicar conceptos de porcentaje, historia de los porcentajes,
# múltiples métodos de cálculo, verificaciones, etc. → 400+ tokens

# ✅ BIEN: Limitar explícitamente los pasos
def calcular_con_cot_controlado(problema: str, max_pasos: int = 5) -> str:
    """CoT con límite explícito de pasos para respuestas concisas."""
    prompt = f"""{problema}

Resuelve en MÁXIMO {max_pasos} pasos concisos. 
Formato:
Paso 1: [acción]
...
Respuesta: [resultado]"""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        max_tokens=200  # Límite de tokens también
    )
    return response.choices[0].message.content

Anti-Pattern 5: Ignorar la Incertidumbre

# ❌ MAL: El modelo afirma con certeza cuando hay incertidumbre real
problema_incierto = "¿Subirá la bolsa mañana?"
# Sin CoT: "Sí, subirá" o "No, bajará"
# Con CoT: Genera 10 párrafos de análisis que terminan en una conclusión igual de incierta

# ✅ BIEN: Usar CoT que incluya estimación de incertidumbre
def razonar_con_incertidumbre(pregunta: str) -> dict:
    """Razonamiento que explícitamente modela la incertidumbre."""
    prompt = f"""Responde la pregunta usando razonamiento paso a paso.
IMPORTANTE: Sé honesto sobre la incertidumbre. Si no puedes saber algo con certeza, dilo.

Estructura de respuesta:
1. Análisis: [razonamiento con los datos disponibles]
2. Supuestos: [qué debes asumir para responder]
3. Factores de incertidumbre: [qué no sabes que afecta la respuesta]
4. Respuesta: [con nivel de confianza: ALTA/MEDIA/BAJA/MUY_BAJA]

Pregunta: {pregunta}"""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        max_tokens=500
    )
    return {"respuesta_con_incertidumbre": response.choices[0].message.content}

Anti-Pattern 6: Usar CoT para Memorización

# ❌ MAL: CoT para preguntas que el modelo "recuerda"
problema_memorizacion = "¿Cuántos planetas tiene el sistema solar?"
# Con CoT: "El sistema solar fue formado hace X años... tiene planetas tipo roca y gas...
# La IAU redefinió planeta en 2006... actualmente hay 8 planetas. Respuesta: 8"
# Sin CoT: "8"

# La diferencia: CoT añade tokens, introduce posibilidad de error en el razonamiento,
# y el resultado es idéntico

# REGLA: Si la respuesta viene de memorización (no requiere pasos), NO usar CoT


def es_pregunta_factual(pregunta: str) -> bool:
    """
    Heurística para detectar preguntas factuales que no se benefician de CoT.
    """
    indicadores_factual = [
        pregunta.strip().startswith("¿Qué es"),
        pregunta.strip().startswith("¿Quién"),
        pregunta.strip().startswith("¿Cuándo"),
        pregunta.strip().startswith("¿Dónde"),
        "nombre de" in pregunta.lower(),
        "capital de" in pregunta.lower(),
        "fundador de" in pregunta.lower(),
    ]
    
    indicadores_razonamiento = [
        "por qué" in pregunta.lower(),
        "cómo" in pregunta.lower() and "calcul" in pregunta.lower(),
        any(op in pregunta for op in ["+", "-", "×", "÷", "%"]),
        "si...entonces" in pregunta.lower(),
        "¿cuánto" in pregunta.lower() and "descuento" in pregunta.lower(),
    ]
    
    return sum(indicadores_factual) > sum(indicadores_razonamiento)

Anti-Pattern 7: No Validar el Formato de Salida

# ❌ MAL: Asumir que el CoT siempre termina con la respuesta en el formato esperado
def parsear_respuesta_MAL(output_cot: str) -> float:
    """Frágil: asume que el último número es la respuesta."""
    import re
    numeros = re.findall(r'\d+\.?\d*', output_cot)
    return float(numeros[-1])  # Puede capturar un año, un número de paso, etc.


# ✅ BIEN: Instrucción explícita de formato + parsing robusto
def resolver_con_formato_robusto(problema: str) -> dict:
    """CoT con formato de salida controlado y parsing robusto."""
    prompt = f"""{problema}

Piensa paso a paso.

IMPORTANTE: Al final de tu razonamiento, escribe EXACTAMENTE en una nueva línea:
RESULTADO: [número o respuesta, sin texto adicional]

No uses "RESULTADO:" en ningún otro lugar de tu respuesta."""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        max_tokens=600
    )
    output = response.choices[0].message.content
    
    import re
    # Buscar el RESULTADO: al final del texto
    match = re.search(r'^RESULTADO:\s*(.+?)$', output, re.MULTILINE | re.IGNORECASE)
    
    if not match:
        # Fallback: hacer una segunda llamada para extraer la respuesta
        response_extraccion = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "user", "content": prompt},
                {"role": "assistant", "content": output},
                {"role": "user", "content": "Dame SOLO el resultado final en formato: RESULTADO: [número]"}
            ],
            temperature=0,
            max_tokens=50
        )
        output_extraccion = response_extraccion.choices[0].message.content
        match = re.search(r'RESULTADO:\s*(.+?)$', output_extraccion, re.IGNORECASE)
    
    return {
        "razonamiento": output,
        "resultado": match.group(1).strip() if match else None,
        "parse_exitoso": match is not None
    }

Tabla de Diagnóstico: ¿Debo Usar CoT?

def diagnostico_cot(tarea: str) -> dict:
    """
    Árbol de decisión para determinar si usar CoT y qué tipo.
    """
    tarea_lower = tarea.lower()
    
    # Anti-patterns claros: NO usar CoT
    if any(t in tarea_lower for t in ["escribe un", "crea un", "genera un poema", "inventa"]):
        return {
            "usar_cot": False,
            "razon": "Tarea creativa: CoT puede rigidizar el output",
            "alternativa": "Prompt directo con alta temperatura"
        }
    
    if any(t in tarea_lower for t in ["capital de", "qué año", "quién inventó", "cuándo nació"]):
        return {
            "usar_cot": False,
            "razon": "Pregunta factual directa: CoT añade tokens sin valor",
            "alternativa": "Prompt directo con temperature=0"
        }
    
    if any(t in tarea_lower for t in ["tu opinión", "qué prefieres", "cuál es mejor", "recomiéndame"]):
        return {
            "usar_cot": False,
            "razon": "Opinión subjetiva: No hay respuesta 'correcta'",
            "alternativa": "Listar pros y cons, no CoT para tomar decisión"
        }
    
    # Casos donde CoT ayuda definitivamente
    if any(t in tarea_lower for t in ["cuánto es", "calcula", "resuelve", "ecuación"]):
        return {
            "usar_cot": True,
            "tipo": "zero_shot_cot",
            "razon": "Problema matemático: CoT mejora accuracy significativamente"
        }
    
    if any(t in tarea_lower for t in ["por qué", "analiza", "evalúa", "¿es válido"]):
        return {
            "usar_cot": True,
            "tipo": "manual_cot_o_pipeline",
            "razon": "Requiere razonamiento complejo: CoT hace el proceso auditable"
        }
    
    # Caso por defecto: CoT ligero
    return {
        "usar_cot": True,
        "tipo": "zero_shot_cot_ligero",
        "razon": "Tarea con algo de complejidad: CoT puede ayudar",
        "nota": "Monitorea si realmente mejora en tu caso específico"
    }


# Tests
tareas = [
    "Escribe un cuento de terror",
    "Calcula el interés compuesto de $1000 al 5% durante 3 años",
    "¿Capital de México?",
    "¿Es válido el argumento: Si P entonces Q, Q, por lo tanto P?",
    "¿Cuál es el mejor framework de JavaScript?",
    "Analiza las causas de la Primera Guerra Mundial",
]

for tarea in tareas:
    d = diagnostico_cot(tarea)
    estado = "✓ CoT" if d["usar_cot"] else "✗ Sin CoT"
    print(f"{estado:12} | {tarea[:50]:50} | {d['razon'][:60]}")

Cuándo CoT Puede Empeorar Activamente los Resultados

Hay investigación que muestra que CoT puede reducir la accuracy en ciertos casos:

SituaciónPor qué CoT empeoraSolución
Modelos pequeños (<7B)No tienen capacidad para razonamiento coherenteUsar modelo más grande o few-shot
Tareas de memorizacion exactaEl razonamiento puede "distraer" del hecho correctoPrompt directo
Problemas con trampa obviaCoT sobre-analiza y llega a conclusión incorrectaSer directo
Clasificación con criterios simplesRazonamiento puede crear confusión artificialPrompt directo
Generación de código cortoRazonamiento sobre el código puede introducir bugsSolo pedir código
# Ejemplo concreto: CoT puede empeorar en modelos pequeños
def probar_cot_vs_directo_modelo_pequeño(problema: str) -> None:
    """
    Demuestra que CoT puede ser neutral/negativo con modelos menos capaces.
    Nota: gpt-4o-mini es capaz para CoT. Para modelos más pequeños el efecto varía.
    """
    # Con CoT
    r_cot = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{problema}\n\nPiensa paso a paso."}],
        temperature=0, max_tokens=500
    )
    
    # Sin CoT
    r_directo = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": problema}],
        temperature=0, max_tokens=100
    )
    
    print("Con CoT:", r_cot.choices[0].message.content[:200])
    print("\nSin CoT:", r_directo.choices[0].message.content)
    print("\nComparar manualmente cuál es más correcto para este problema específico")

Ejercicios

Ejercicio 1: Identificar tareas que NO deberían usar CoT

De la siguiente lista, clasifica cuáles NO deberían usar CoT y explica por qué:

a) Traducir "Hello World" al español
b) Resolver la ecuación cuadrática x² - 5x + 6 = 0
c) Generar el nombre de un producto innovador
d) Verificar si "Todos los gatos son animales. Misi es un gato. ¿Misi es animal?" es válido
e) Responder "¿Cuándo fue fundada Apple?"

Ver solución
(a) Traducción → NO CoT
- Tarea directa: la traducción no requiere razonamiento, es mapeo lingüístico
- "Hola Mundo" - ningún beneficio de mostrar pasos
- Usar prompt directo

(b) Ecuación cuadrática → SÍ CoT
- Requiere múltiples pasos: calcular discriminante, aplicar fórmula, simplificar
- CoT: b²-4ac = 25-24 = 1. x = (5±1)/2. x₁ = 3, x₂ = 2
- Sin CoT: el modelo puede dar respuesta incorrecta sin mostrar el proceso

(c) Nombre de producto → NO CoT
- Tarea creativa: CoT genera nombres mecánicos y poco originales
- "Paso 1: Identificar la industria. Paso 2: Buscar palabras relacionadas..." → resultado genérico
- Mejor: prompt creativo con temperatura alta

(d) Razonamiento lógico → SÍ CoT
- Requiere identificar premisas, aplicar silogismo, verificar validez
- Sin CoT el modelo puede errar; con CoT es más preciso y verificable

(e) Pregunta factual → NO CoT
- "1976" o "1 de abril de 1976"
- CoT añadiría: "Apple es una empresa tecnológica... fundada por Wozniak y Jobs...
  el año exacto fue 1976." → 3x más tokens para la misma respuesta

Ejercicio 2: Detectar razonamiento fabricado

El siguiente output de CoT contiene un error. Encuéntralo:

Problema: Una tienda vende 240 productos por día. En 5 días, ¿cuántos productos vende?

Razonamiento:
- Ventas diarias: 240 productos
- Días: 5
- Total: 240 × 5 = 1,200
- Verificación: 1,200 / 5 = 240 ✓
- Respuesta: 1,200 productos
Ver solución

Sorpresa: Este razonamiento ES correcto. 240 × 5 = 1,200, y la verificación también es correcta.

Este ejercicio tiene una trampa: el objetivo es que no detectes un error donde no lo hay. El sesgo de "buscar errores" puede llevar a cuestionar razonamientos correctos.

Lección: La detección de razonamiento fabricado debe ser objetiva, no sesgada hacia encontrar errores. Verifica cada paso matemáticamente:

  • 240 × 5 = 1,200? Sí ✓
  • 1,200 / 5 = 240? Sí ✓
# Implementación robusta de verificación objetiva
import re

def verificar_operaciones_en_razonamiento(texto: str) -> dict:
    errores = []
    correctas = []
    
    for match in re.finditer(r'(\d+)\s*[×x\*]\s*(\d+)\s*=\s*([\d,]+)', texto):
        a = int(match.group(1))
        b = int(match.group(2))
        declarado = int(match.group(3).replace(',', ''))
        correcto = a * b
        
        if correcto == declarado:
            correctas.append(f"{a} × {b} = {declarado} ✓")
        else:
            errores.append(f"{a} × {b} = {declarado} ✗ (correcto: {correcto})")
    
    return {
        "operaciones_correctas": correctas,
        "errores": errores,
        "razonamiento_valido": len(errores) == 0
    }

texto_razonamiento = """240 × 5 = 1,200. Verificación: 1,200 / 5 = 240"""
print(verificar_operaciones_en_razonamiento(texto_razonamiento))

Ejercicio 3: Anti-pattern de confianza ciega

Escribe un sistema que use CoT para cálculos de precios, pero que verifique matemáticamente la respuesta antes de usarla en producción.

Ver solución
from openai import OpenAI
import re

client = OpenAI()

def calcular_precio_con_validacion(
    precio_base: float,
    descuento_pct: float,
    impuesto_pct: float
) -> dict:
    """
    Calcula precio final con:
    1. CoT para explicación al usuario
    2. Verificación matemática independiente del resultado del LLM
    """
    # Cálculo correcto (determinístico, siempre correcto)
    precio_con_descuento = precio_base * (1 - descuento_pct / 100)
    precio_final_correcto = precio_con_descuento * (1 + impuesto_pct / 100)
    
    # CoT para explicación
    prompt = f"""Explica el cálculo del precio final paso a paso.
Precio base: ${precio_base}
Descuento: {descuento_pct}%
Impuesto: {impuesto_pct}%

Muestra cada paso. Al final escribe: PRECIO FINAL: $[número]"""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0, max_tokens=300
    )
    output = response.choices[0].message.content
    
    # Extraer precio del LLM
    match = re.search(r'PRECIO FINAL:\s*\$?([\d.]+)', output)
    precio_llm = float(match.group(1)) if match else None
    
    # Validar que el LLM llegó al resultado correcto
    discrepancia = None
    if precio_llm is not None:
        diferencia = abs(precio_llm - precio_final_correcto)
        if diferencia > 0.01:  # Tolerancia de 1 centavo
            discrepancia = f"LLM calculó ${precio_llm:.2f}, correcto es ${precio_final_correcto:.2f}"
    
    return {
        "precio_final": precio_final_correcto,  # SIEMPRE usar el cálculo determinístico
        "precio_llm": precio_llm,
        "discrepancia_detectada": discrepancia,
        "explicacion_usuario": output,
        "validacion": "OK" if discrepancia is None else "ERROR_DETECTADO"
    }


# Prueba con varios escenarios
escenarios = [
    (100, 20, 16),   # $100, 20% descuento, 16% IVA → $80 × 1.16 = $92.80
    (250, 10, 0),    # $250, 10% descuento, sin IVA → $225
    (1000, 30, 10),  # $1000, 30% descuento, 10% IVA → $700 × 1.10 = $770
]

for precio, desc, imp in escenarios:
    r = calcular_precio_con_validacion(precio, desc, imp)
    estado = "✓" if r["validacion"] == "OK" else f"⚠️  {r['discrepancia_detectada']}"
    print(f"${precio} -{desc}% +{imp}% = ${r['precio_final']:.2f} | {estado}")

Ejercicio 4: Construir el árbol de decisión completo

Implementa una función que dado cualquier tipo de tarea, decida: sin CoT / zero-shot CoT / manual CoT / pipeline, con justificación.

Ver solución
from enum import Enum

class EstrategiaCoT(Enum):
    SIN_COT = "Sin CoT"
    ZERO_SHOT = "Zero-Shot CoT"
    MANUAL_COT = "Manual CoT (Few-Shot)"
    PIPELINE = "Pipeline Multi-Step"

def seleccionar_estrategia(tarea: str, contexto: dict = None) -> dict:
    """
    Árbol de decisión completo para selección de estrategia CoT.
    
    Args:
        tarea: Descripción de la tarea
        contexto: {'presupuesto_tokens': 'bajo/medio/alto', 'criticidad': 'baja/alta'}
    """
    contexto = contexto or {}
    tarea_lower = tarea.lower()
    
    # Nivel 1: ¿Es la tarea creativa o de opinión?
    creativo = any(w in tarea_lower for w in ['escribe', 'crea', 'genera', 'inventa', 'diseña un texto'])
    opinion = any(w in tarea_lower for w in ['mejor', 'recomienda', 'prefieres', 'opinión'])
    
    if creativo or opinion:
        return {
            "estrategia": EstrategiaCoT.SIN_COT,
            "razon": f"Tarea {'creativa' if creativo else 'de opinión'}: CoT rigidiza/sesga",
            "temperatura": 0.7 if creativo else 0.3
        }
    
    # Nivel 2: ¿Es factual directa?
    factual = any(w in tarea_lower for w in ['capital de', 'cuándo fue', 'quién inventó', 'traduce'])
    
    if factual:
        return {
            "estrategia": EstrategiaCoT.SIN_COT,
            "razon": "Pregunta factual: no requiere razonamiento",
            "temperatura": 0
        }
    
    # Nivel 3: ¿Requiere múltiples sub-tareas o inspección?
    complejo = (
        len(tarea) > 300 or
        tarea.count('\n') > 5 or
        any(w in tarea_lower for w in ['primero...luego', 'varias etapas', 'analiza y genera'])
    )
    
    if complejo:
        return {
            "estrategia": EstrategiaCoT.PIPELINE,
            "razon": "Problema complejo con múltiples sub-tareas",
            "n_etapas": 4,
            "temperatura": 0
        }
    
    # Nivel 4: ¿Hay dominio específico que requiera razonamiento exacto?
    dominio_especifico = any(w in tarea_lower for w in [
        'código', 'debug', 'algoritmo', 'contrato', 'legal'
    ])
    
    if dominio_especifico:
        return {
            "estrategia": EstrategiaCoT.MANUAL_COT,
            "razon": "Dominio específico: ejemplos de razonamiento mejoran consistencia",
            "n_ejemplos": 2,
            "temperatura": 0
        }
    
    # Default: Zero-Shot CoT para razonamiento moderado
    return {
        "estrategia": EstrategiaCoT.ZERO_SHOT,
        "razon": "Tarea de razonamiento: Zero-Shot CoT es el balance óptimo",
        "temperatura": 0
    }

# Demo
tareas_demo = [
    "Escribe un soneto sobre la tecnología",
    "¿Capital de Japón?",
    "Calcula el valor futuro de $5000 al 7% anual durante 10 años con interés compuesto",
    "Analiza este contrato de arrendamiento, identifica cláusulas abusivas, propón alternativas",
    "Debug este código Python: def sum(a,b): return a-b",
]

print(f"{'Tarea':50} | {'Estrategia':25} | Razón")
print("-" * 100)
for tarea in tareas_demo:
    d = seleccionar_estrategia(tarea)
    print(f"{tarea[:50]:50} | {d['estrategia'].value:25} | {d['razon'][:40]}")

Ejercicio 5: Sistema de detección de alucinaciones en razonamiento

Implementa un verificador que detecte cuando el modelo usa información que no estaba en el problema original.

Ver solución
from openai import OpenAI

client = OpenAI()

def detectar_informacion_externa(
    problema_original: str,
    razonamiento_cot: str
) -> dict:
    """
    Detecta cuando el razonamiento incluye información no proporcionada.
    """
    prompt = f"""Eres un auditor. Verifica si el razonamiento usa SOLO la información del problema.

PROBLEMA ORIGINAL (fuente de verdad):
{problema_original}

RAZONAMIENTO GENERADO:
{razonamiento_cot}

Identifica: ¿Hay información en el razonamiento que NO está en el problema original?
- Datos inventados (números, porcentajes, fechas no mencionados)
- Suposiciones no declaradas
- Hechos del mundo real no relevantes que el modelo "importó"
- Generalizaciones sobre el problema que van más allá de los datos dados

Responde:
INFORMACIÓN_EXTERNA: [Sí/No]
LISTA: [qué información fue añadida, o "Ninguna"]"""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0, max_tokens=300
    )
    output = response.choices[0].message.content
    
    import re
    tiene_externa = "sí" in output.lower()[:100] or "información_externa: sí" in output.lower()
    
    return {
        "tiene_informacion_externa": tiene_externa,
        "analisis": output
    }


# Prueba
problema = "Juan tiene 5 manzanas y María tiene 3. ¿Cuántas tienen juntos?"
razonamiento_limpio = "Juan: 5. María: 3. Total: 5 + 3 = 8 manzanas."
razonamiento_contaminado = "Juan tiene 5 manzanas. María tiene 3. Una manzana pesa aproximadamente 200g, por lo que tienen 1.6 kg de manzanas juntos. En total tienen 8 manzanas."

print("Razonamiento limpio:")
print(detectar_informacion_externa(problema, razonamiento_limpio))

print("\nRazonamiento contaminado:")
print(detectar_informacion_externa(problema, razonamiento_contaminado))

Resumen

  • No usar CoT en: tareas creativas, opinión subjetiva, preguntas factuales simples, memorización directa
  • CoT fabricado: Razonamiento persuasivo que parece correcto pero no lo es; detectar verificando operaciones matemáticamente
  • Alucinaciones en reasoning: El modelo puede inventar datos, citas o premisas dentro del razonamiento; usar instrucción "solo datos del contexto"
  • Anti-patterns clave: Overuse de CoT, confianza ciega sin verificación, razonamiento demasiado largo, ignorar incertidumbre, mal parsing de output
  • Regla de oro: Para cálculos críticos en producción, verificar el resultado del LLM con código determinístico independiente

Recursos adicionales

  1. The False Promise of Imitating Proprietary LLMs (Gudibande et al., 2023)
  2. Sycophancy to Subterfuge: Investigating Reward Tampering in Language Models
  3. Measuring Mathematical Problem Solving With the MATH Dataset
  4. TruthfulQA: Measuring How Models Mimic Human Falsehoods
  5. When Not to Trust Language Models (Kadavath et al., 2022)
  6. Calibration of Large Language Models Using Their Generations