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

5. Verification Patterns en CoT

Descripción

CoT puede producir razonamiento que suena perfectamente lógico pero llega a conclusiones incorrectas. Esta cápsula cubre cuatro patrones de verificación para detectar y corregir estos errores: self-verification, backward verification, cross-checking con restricciones, y confidence scoring. También implementamos un sistema de two-pass verification donde un segundo LLM actúa como auditor independiente.

Tiempo estimado: 75-90 minutos


El Problema: CoT Correcto ≠ Respuesta Correcta

Considera este caso real de fallo de CoT:

Problema: En una sala hay 3 mesas. Cada mesa tiene 4 sillas. 
          Entran 5 personas. ¿Cuántas sillas quedan vacías?

CoT del modelo:
- Total de sillas: 3 mesas × 4 sillas = 12 sillas
- Personas que entran: 5
- Sillas ocupadas: 5
- Sillas vacías: 12 - 5 = 7

Respuesta: 7 ✓ (CORRECTO)

Ahora el mismo modelo con un problema ligeramente más complejo:

Problema: En una sala hay 3 mesas. Cada mesa tiene 4 sillas. 
          Si 7 de las sillas ya tienen objetos encima y entran 5 personas,
          ¿cuántas sillas están disponibles para las personas?

CoT del modelo:
- Total sillas: 3 × 4 = 12
- Sillas con objetos: 7
- Sillas disponibles: 12 - 7 = 5
- Personas: 5
- Todas las personas pueden sentarse.
- Sillas disponibles para personas: 5

Respuesta: 5 (INCORRECTO - después de que se sienten las 5 personas, 
             quedarían 0 sillas disponibles, pero si la pregunta es 
             "cuántas hay antes de que entren", la respuesta es 5.
             La ambigüedad no fue detectada.)

Los patrones de verificación detectan estos fallos.


Patrón 1: Self-Verification

El modelo revisa su propio trabajo al final del razonamiento.

Implementación Básica

from openai import OpenAI

client = OpenAI()

SELF_VERIFY_COT = """Resuelve el problema y luego verifica tu respuesta.

PARTE 1 - RESOLUCIÓN:
Piensa paso a paso. Muestra cada operación.

PARTE 2 - VERIFICACIÓN:
Después de obtener tu respuesta:
- ¿Cada paso matemático es correcto? (verifica las operaciones)
- ¿La respuesta tiene sentido en el contexto? (¿es un número razonable?)
- ¿Respondiste exactamente lo que se preguntó? (no más, no menos)
- Si encuentras algún error, corrígelo aquí.

PARTE 3 - RESPUESTA FINAL:
Respuesta verificada: [tu respuesta corregida si aplicó, o la original si era correcta]

Problema: {problema}
"""


def resolver_con_self_verification(problema: str) -> dict:
    """
    Resuelve un problema con auto-verificación integrada.
    
    Returns:
        dict con 'razonamiento', 'verificacion', 'respuesta_final'
    """
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "user",
                "content": SELF_VERIFY_COT.format(problema=problema)
            }
        ],
        temperature=0,
        max_tokens=900
    )
    output = response.choices[0].message.content
    
    # Parsear las secciones
    import re
    partes = {
        "razonamiento": "",
        "verificacion": "",
        "respuesta_final": ""
    }
    
    # Extraer respuesta verificada
    match_respuesta = re.search(
        r'Respuesta verificada:\s*(.+?)(?:\n|$)', 
        output, re.IGNORECASE
    )
    if match_respuesta:
        partes["respuesta_final"] = match_respuesta.group(1).strip()
    
    partes["output_completo"] = output
    
    return partes

Self-Verification Explícita con Checklist

SELF_VERIFY_CHECKLIST = """Resuelve el problema. Luego completa el checklist de verificación.

=== RESOLUCIÓN ===
{problema}

Razonamiento:

=== CHECKLIST DE VERIFICACIÓN ===
Después de resolver, responde cada punto con ✓ o ✗ y una nota breve:

[ ] 1. ¿Interpretaste correctamente la pregunta?
[ ] 2. ¿Usaste todos los datos proporcionados?
[ ] 3. ¿Las operaciones matemáticas son correctas?
[ ] 4. ¿Las unidades son consistentes (km, kg, $, etc.)?
[ ] 5. ¿La magnitud de la respuesta es razonable?
[ ] 6. ¿Respondiste exactamente lo que se preguntó?

=== RESPUESTA FINAL VERIFICADA ===
[Si todos los checks son ✓: confirmar respuesta. Si alguno es ✗: corregir y dar nueva respuesta]
"""

Limitaciones de Self-Verification

# ⚠️ El modelo tiende a auto-confirmarse incluso cuando está equivocado
# Esto se llama "sycophantic verification" o verificación aduladora

def medir_tasa_autocorreccion(problemas_con_error_inducido: list) -> dict:
    """
    Mide qué tan a menudo el modelo corrige sus propios errores.
    
    Nota: En la práctica, los modelos se auto-confirman ~70-80% del tiempo
    incluso cuando el razonamiento tiene un error.
    """
    auto_corregidos = 0
    confirmados_incorrectamente = 0
    
    for problema, respuesta_correcta in problemas_con_error_inducido:
        resultado = resolver_con_self_verification(problema)
        
        if respuesta_correcta in str(resultado["respuesta_final"]):
            auto_corregidos += 1
        else:
            confirmados_incorrectamente += 1
    
    total = len(problemas_con_error_inducido)
    return {
        "tasa_auto_correccion": auto_corregidos / total,
        "tasa_confirmacion_error": confirmados_incorrectamente / total,
        "conclusion": "Self-verification no es suficiente para problemas críticos"
    }

Patrón 2: Backward Verification

Después de obtener la respuesta, el modelo la "sustituye hacia atrás" para verificar que resuelve el problema original.

Implementación: Backward Check Matemático

BACKWARD_VERIFY_COT = """Resuelve el problema paso a paso.

PASO FORWARD (Resolución):
Calcula la respuesta.

PASO BACKWARD (Verificación):
Toma tu respuesta y aplícala al problema original:
- Sustituye el valor obtenido en las condiciones del problema
- ¿Se cumplen TODAS las condiciones originales?
- Si sí: VERIFICADO ✓
- Si no: Identifica el error y resuelve de nuevo

RESPUESTA FINAL: [respuesta verificada]

Problema: {problema}
"""


def resolver_con_backward_check(problema: str) -> dict:
    """Resuelve con verificación backward."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": BACKWARD_VERIFY_COT.format(problema=problema)}],
        temperature=0,
        max_tokens=800
    )
    output = response.choices[0].message.content
    
    verificado = "VERIFICADO ✓" in output or "VERIFICADO" in output.upper()
    
    import re
    match = re.search(r'RESPUESTA FINAL:\s*(.+?)(?:\n|$)', output, re.IGNORECASE)
    
    return {
        "output": output,
        "verificado": verificado,
        "respuesta": match.group(1).strip() if match else None
    }


# Ejemplos donde backward check detecta errores
PROBLEMAS_BACKWARD_TEST = [
    # Problema 1: Ecuación lineal
    "Resuelve: 3x + 7 = 22. Valor de x:",
    # Backward: ¿3(5)+7=22? 15+7=22 ✓
    
    # Problema 2: Porcentaje inverso
    "Un producto con 20% de descuento cuesta $80. ¿Cuál era el precio original?",
    # Forward (común error): 80 + 20%*80 = 96 ✗
    # Backward: ¿96 - 20%*96 = 80? 96-19.2=76.8 ≠ 80 → ERROR
    # Correcto: 80/0.80 = $100. Backward: 100-20%*100=80 ✓
    
    # Problema 3: Tasa de trabajo
    "Si A hace un trabajo en 6 horas y B en 4 horas, ¿cuánto tardan juntos?",
    # Correcto: 1/6 + 1/4 = 5/12 → 12/5 = 2.4 horas
    # Backward: ¿En 2.4h, A hace 2.4/6=0.4 del trabajo, B hace 2.4/4=0.6. 0.4+0.6=1 ✓
]

if __name__ == "__main__":
    for problema in PROBLEMAS_BACKWARD_TEST:
        resultado = resolver_con_backward_check(problema)
        print(f"\nProblema: {problema[:60]}...")
        print(f"Verificado: {resultado['verificado']}")
        print(f"Respuesta: {resultado['respuesta']}")

Backward Check para Lógica

BACKWARD_LOGIC_COT = """Analiza el argumento lógico.

ANÁLISIS FORWARD:
Identifica premisas y analiza si la conclusión se sigue.

VERIFICACIÓN BACKWARD:
Para el argumento, busca un CONTRAEJEMPLO:
- Asume que las premisas son verdaderas Y la conclusión es falsa
- ¿Es posible ese escenario?
- Si es posible → argumento INVÁLIDO (la conclusión no se sigue necesariamente)
- Si es imposible → argumento VÁLIDO

VEREDICTO: VÁLIDO o INVÁLIDO

Argumento: {argumento}
"""

Patrón 3: Cross-Checking con Restricciones

Para problemas con múltiples condiciones, verificar cada una explícitamente.

Implementación: Check de Restricciones

from dataclasses import dataclass

@dataclass
class RestriccionCheck:
    descripcion: str
    cumplida: bool | None = None
    nota: str = ""


CONSTRAINT_VERIFY_COT = """Resuelve el problema y luego verifica CADA restricción.

Problema: {problema}
Restricciones:
{restricciones}

=== RESOLUCIÓN ===
[Tu solución aquí]

=== VERIFICACIÓN DE RESTRICCIONES ===
Para cada restricción, evalúa si tu solución la cumple:
{checks_template}

=== RESULTADO ===
- Total restricciones: {n_restricciones}
- Cumplidas: ?/
- ¿Solución válida? SÍ/NO

Si alguna restricción falla, ajusta la solución y vuelve a verificar.
"""


def resolver_con_constraint_check(
    problema: str,
    restricciones: list[str]
) -> dict:
    """
    Resuelve y verifica cada restricción explícitamente.
    
    Args:
        problema: El problema a resolver
        restricciones: Lista de restricciones que debe cumplir la solución
    
    Returns:
        dict con solución, checks por restricción, y validez global
    """
    restricciones_texto = "\n".join(f"- R{i+1}: {r}" for i, r in enumerate(restricciones))
    checks_template = "\n".join(
        f"R{i+1} ({r[:40]}...): ✓/✗" for i, r in enumerate(restricciones)
    )
    
    prompt = CONSTRAINT_VERIFY_COT.format(
        problema=problema,
        restricciones=restricciones_texto,
        checks_template=checks_template,
        n_restricciones=len(restricciones)
    )
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        max_tokens=1000
    )
    output = response.choices[0].message.content
    
    # Contar checks
    import re
    checks_cumplidos = len(re.findall(r'✓', output))
    checks_fallidos = len(re.findall(r'✗', output))
    
    return {
        "output_completo": output,
        "restricciones_cumplidas": checks_cumplidos,
        "restricciones_fallidas": checks_fallidos,
        "solucion_valida": checks_fallidos == 0 and checks_cumplidos > 0
    }


# Ejemplo: Diseño de base de datos con restricciones
problema_bd = "Diseña el esquema de tabla para un sistema de pedidos de e-commerce"

restricciones_bd = [
    "Debe almacenar usuario, productos, cantidades y precios",
    "Un pedido puede tener múltiples productos",
    "Debe registrar la fecha y hora del pedido",
    "Debe permitir trackear el estado (pendiente, enviado, entregado)",
    "Debe almacenar dirección de envío",
    "Las relaciones deben estar en 3NF (tercera forma normal)",
]

if __name__ == "__main__":
    resultado = resolver_con_constraint_check(problema_bd, restricciones_bd)
    print(resultado["output_completo"])
    print(f"\n✓ Cumplidas: {resultado['restricciones_cumplidas']}")
    print(f"✗ Fallidas: {resultado['restricciones_fallidas']}")
    print(f"Solución válida: {resultado['solucion_valida']}")

Patrón 4: Confidence Scoring

El modelo asigna un score de confianza a su respuesta y explica el nivel de certeza.

Implementación: Confidence Score Estructurado

CONFIDENCE_COT = """Resuelve el problema. Al final, asigna un score de confianza explicando por qué.

=== RESOLUCIÓN ===
Piensa paso a paso.

=== CONFIANZA ===
Evalúa tu respuesta en estas dimensiones:

1. CERTEZA DEL PROCESO (0-1): ¿Estás seguro de los pasos que seguiste?
2. VERIFICABILIDAD (0-1): ¿Pudiste verificar la respuesta? ¿Cómo?
3. AMBIGÜEDAD DEL PROBLEMA (0-1): ¿El problema era claro? (1=muy claro, 0=muy ambiguo)
4. CONOCIMIENTO DEL DOMINIO (0-1): ¿Tienes certeza del conocimiento que aplicaste?

SCORE FINAL: Promedio de las 4 dimensiones = [X.XX]

Interpretación:
- 0.9-1.0: Muy alta confianza, respuesta casi certamente correcta
- 0.7-0.9: Alta confianza, probablemente correcta pero verifica
- 0.5-0.7: Confianza media, puede haber errores
- < 0.5: Baja confianza, verificar por otro medio

RESPUESTA FINAL: [respuesta]
CONFIANZA: [score] - [interpretación]

Problema: {problema}
"""


def resolver_con_confidence(problema: str) -> dict:
    """Resuelve con score de confianza detallado."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": CONFIDENCE_COT.format(problema=problema)}],
        temperature=0,
        max_tokens=800
    )
    output = response.choices[0].message.content
    
    import re
    
    # Extraer score de confianza
    score_match = re.search(r'SCORE FINAL:.*?=\s*([\d.]+)', output, re.IGNORECASE)
    confianza_match = re.search(r'CONFIANZA:\s*([\d.]+)', output, re.IGNORECASE)
    respuesta_match = re.search(r'RESPUESTA FINAL:\s*(.+?)(?:\n|$)', output, re.IGNORECASE)
    
    score = None
    if score_match:
        try:
            score = float(score_match.group(1))
        except ValueError:
            pass
    elif confianza_match:
        try:
            score = float(confianza_match.group(1))
        except ValueError:
            pass
    
    return {
        "output": output,
        "respuesta": respuesta_match.group(1).strip() if respuesta_match else None,
        "confianza_score": score,
        "confianza_nivel": (
            "muy_alta" if score and score >= 0.9 else
            "alta" if score and score >= 0.7 else
            "media" if score and score >= 0.5 else
            "baja" if score else "desconocida"
        )
    }


# Problemas con diferentes niveles de dificultad
PROBLEMAS_CONFIDENCE_TEST = [
    "¿Cuánto es 15 × 20?",  # Esperamos confianza muy alta
    "Si la economía crece 3% este año, ¿cuánto crecerá el año que viene?",  # Esperamos baja confianza
    "¿Es p=NP?",  # Esperamos baja confianza (problema abierto)
    "En un triángulo rectángulo con catetos 3 y 4, ¿cuánto mide la hipotenusa?",  # Alta confianza
]

if __name__ == "__main__":
    for problema in PROBLEMAS_CONFIDENCE_TEST:
        resultado = resolver_con_confidence(problema)
        print(f"\nProblema: {problema[:60]}")
        print(f"Respuesta: {resultado['respuesta']}")
        print(f"Confianza: {resultado['confianza_score']} ({resultado['confianza_nivel']})")

Patrón 5: Two-Pass Verification (El Más Robusto)

Un segundo LLM actúa como verificador independiente. Este es el patrón más robusto porque elimina el bias de auto-confirmación.

Implementación: Two-Pass con Roles Diferentes

def two_pass_verification(
    problema: str,
    temperatura_solver: float = 0.0,
    temperatura_verifier: float = 0.0
) -> dict:
    """
    Two-Pass Verification:
    - Pass 1: El "Solver" resuelve el problema
    - Pass 2: El "Verifier" verifica la solución como auditor independiente
    
    El Verifier recibe el problema original + la solución propuesta,
    pero NO el razonamiento del Solver (para evitar anchoring bias).
    
    Args:
        problema: El problema a resolver
        temperatura_solver: Temperatura para el solver (0 = determinístico)
        temperatura_verifier: Temperatura para el verificador
    
    Returns:
        dict con solución, verificación, y veredicto final
    """
    # PASS 1: Solver resuelve
    prompt_solver = f"""Eres un solucionador experto. Resuelve el siguiente problema paso a paso.
Al final escribe EXACTAMENTE: "SOLUCIÓN: [tu respuesta]"

Problema: {problema}"""
    
    respuesta_solver = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt_solver}],
        temperature=temperatura_solver,
        max_tokens=700
    )
    solucion_completa = respuesta_solver.choices[0].message.content
    
    # Extraer solo la respuesta final para pasarla al verificador
    import re
    match = re.search(r'SOLUCIÓN:\s*(.+?)(?:\n|$)', solucion_completa, re.IGNORECASE)
    respuesta_propuesta = match.group(1).strip() if match else solucion_completa[-100:]
    
    # PASS 2: Verifier audita (recibe el problema + respuesta propuesta, NO el razonamiento)
    prompt_verifier = f"""Eres un auditor de soluciones matemáticas/lógicas. Tu trabajo es verificar si una solución propuesta es correcta.

INSTRUCCIONES:
- Resuelve el problema INDEPENDIENTEMENTE (no te fíes de la solución propuesta)
- Compara tu solución con la propuesta
- Da tu veredicto: CORRECTO o INCORRECTO
- Si es incorrecto, da la respuesta correcta

Problema original: {problema}

Solución propuesta: {respuesta_propuesta}

VERIFICACIÓN:"""
    
    respuesta_verifier = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt_verifier}],
        temperature=temperatura_verifier,
        max_tokens=500
    )
    verificacion = respuesta_verifier.choices[0].message.content
    
    # Determinar veredicto
    verificacion_upper = verificacion.upper()
    if "CORRECTO" in verificacion_upper and "INCORRECTO" not in verificacion_upper:
        veredicto = "APROBADO"
    elif "INCORRECTO" in verificacion_upper:
        veredicto = "RECHAZADO"
    else:
        veredicto = "INDETERMINADO"
    
    # Extraer solución corregida si fue rechazada
    solucion_final = respuesta_propuesta
    if veredicto == "RECHAZADO":
        match_correccion = re.search(
            r'(?:correcta|correctos?)[:\s]+(.+?)(?:\n|$)', 
            verificacion, re.IGNORECASE
        )
        if match_correccion:
            solucion_final = match_correccion.group(1).strip()
    
    return {
        "problema": problema,
        "solucion_solver": solucion_completa,
        "respuesta_propuesta": respuesta_propuesta,
        "verificacion_auditor": verificacion,
        "veredicto": veredicto,
        "respuesta_final": solucion_final,
        "tokens_total": (
            respuesta_solver.usage.total_tokens + 
            respuesta_verifier.usage.total_tokens
        )
    }


# Benchmark: Medir mejora de two-pass vs. single-pass
def benchmark_verificacion(
    problemas: list[tuple[str, str]]  # (problema, respuesta_correcta)
) -> dict:
    """Compara single-pass vs. two-pass en accuracy."""
    single_pass_ok = 0
    two_pass_ok = 0
    
    for problema, correcta in problemas:
        # Single pass
        r_single = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problema}\n\nPiensa paso a paso."}],
            temperature=0, max_tokens=500
        )
        if correcta in r_single.choices[0].message.content:
            single_pass_ok += 1
        
        # Two pass
        r_two = two_pass_verification(problema)
        if correcta in r_two["respuesta_final"]:
            two_pass_ok += 1
    
    n = len(problemas)
    return {
        "single_pass_accuracy": single_pass_ok / n,
        "two_pass_accuracy": two_pass_ok / n,
        "mejora": (two_pass_ok - single_pass_ok) / n
    }

Patrón 6: Ensemble Verification

Combina múltiples estrategias de verificación para máxima confianza.

def ensemble_verification(
    problema: str,
    n_self_consistency: int = 3
) -> dict:
    """
    Ensemble de técnicas de verificación:
    1. Self-Consistency (N soluciones independientes)
    2. Backward verification de la respuesta mayoritaria
    3. Confidence score final
    
    Args:
        problema: El problema a resolver
        n_self_consistency: Número de soluciones para self-consistency
    
    Returns:
        dict con respuesta de alta confianza y metadatos de verificación
    """
    from collections import Counter
    
    # Paso 1: Self-Consistency - generar N soluciones
    soluciones = []
    for _ in range(n_self_consistency):
        r = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problema}\n\nPiensa paso a paso."}],
            temperature=0.7,  # Temperatura moderada para diversidad
            max_tokens=500
        )
        output = r.choices[0].message.content
        
        # Extraer número final
        import re
        match = re.search(r'(?:respuesta|therefore|total|=)\s*:?\s*\$?([\d,.]+)', output.lower())
        respuesta = match.group(1).replace(',', '') if match else None
        soluciones.append(respuesta)
    
    # Votar la respuesta mayoritaria
    votos = Counter([s for s in soluciones if s])
    if not votos:
        return {"error": "No se pudo extraer ninguna respuesta"}
    
    respuesta_mayoridad, n_votos = votos.most_common(1)[0]
    confianza_sc = n_votos / n_self_consistency
    
    # Paso 2: Backward verification de la respuesta mayoritaria
    prompt_backward = f"""
Problema: {problema}
Respuesta propuesta: {respuesta_mayoridad}

Verifica que esta respuesta sea correcta sustituyéndola en el problema.
¿Se cumplen todas las condiciones? Responde VERIFICADO ✓ o ERROR ✗.
"""
    r_backward = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt_backward}],
        temperature=0,
        max_tokens=300
    )
    verificacion_backward = r_backward.choices[0].message.content
    backward_ok = "VERIFICADO" in verificacion_backward.upper()
    
    # Calcular confianza final
    confianza_final = confianza_sc * (1.0 if backward_ok else 0.5)
    
    return {
        "respuesta_final": respuesta_mayoridad,
        "distribucion_votos": dict(votos),
        "confianza_self_consistency": confianza_sc,
        "backward_verificado": backward_ok,
        "confianza_final": confianza_final,
        "nivel_confianza": (
            "MUY ALTA" if confianza_final >= 0.8 else
            "ALTA" if confianza_final >= 0.6 else
            "MEDIA" if confianza_final >= 0.4 else
            "BAJA"
        )
    }

Tabla Comparativa: Patrones de Verificación

PatrónCosto (llamadas)EfectividadMejor para
Sin verificación1xLínea basePrototipos, baja criticidad
Self-verification1x (más tokens)+10-15%Balance costo/beneficio
Backward check1x (más tokens)+20-30%Matemáticas, ecuaciones
Constraint check1x (más tokens)+25-35%Problemas multi-restricción
Confidence score1x (más tokens)InformacionalMonitoreo de calidad
Two-pass (auditor)2x+30-40%Aplicaciones críticas
Ensemble3-5x+40-60%Máxima precisión requerida

Troubleshooting

Problema 1: El modelo se auto-confirma incorrectamente

# ❌ El modelo dice "VERIFICADO ✓" aunque la respuesta sea incorrecta
# Esto pasa ~70% del tiempo en self-verification

# ✅ Solución 1: Usar un segundo modelo como auditor (two-pass)
resultado = two_pass_verification(problema)

# ✅ Solución 2: Pedir explícitamente buscar errores, no confirmar
VERIFICACION_CRITICA = """Tu trabajo es encontrar errores, no confirmar que está bien.
Asume que PUEDE haber un error y busca activamente.

Problema: {problema}
Solución propuesta: {solucion}

Lista todos los posibles problemas con esta solución.
Si no encuentras ninguno, escribe "Sin errores detectados" y explica por qué.
"""

Problema 2: La verificación añade demasiado costo

# ✅ Usar verificación solo para llamadas críticas
def resolver_con_verificacion_selectiva(
    problema: str,
    umbral_complejidad: int = 3
) -> dict:
    """Solo usa two-pass si el problema parece complejo."""
    
    # Estimación rápida de complejidad
    palabras_complejidad = ['si...entonces', 'múltiples', 'todos', 'algunos', 
                             'excepto', 'a menos que', 'condición', 'restricción']
    complejidad = sum(1 for w in palabras_complejidad if w in problema.lower())
    
    if complejidad >= umbral_complejidad:
        return two_pass_verification(problema)
    else:
        # Single pass simple
        r = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problema}\n\nPiensa paso a paso."}],
            temperature=0, max_tokens=500
        )
        return {"respuesta_final": r.choices[0].message.content, "tipo": "single_pass"}

Problema 3: La confianza del modelo está incorrectamente calibrada

# ❌ El modelo reporta 0.9 de confianza en una respuesta incorrecta
# Los LLMs tienden a ser "overconfident" (exceso de confianza)

# ✅ Calibrar confianza con datos reales
def calibrar_confianza(
    problemas_con_respuesta: list[tuple[str, str]],
    n_muestras: int = 20
) -> dict:
    """
    Mide si el confidence score reportado está calibrado.
    Un modelo bien calibrado debería tener ~90% accuracy en los casos 
    donde reporta 0.9 de confianza.
    """
    por_nivel = {"alta": [], "media": [], "baja": []}
    
    for problema, correcta in problemas_con_respuesta[:n_muestras]:
        resultado = resolver_con_confidence(problema)
        nivel = resultado["confianza_nivel"]
        
        if nivel in ["muy_alta", "alta"]:
            es_correcta = correcta in str(resultado["respuesta"] or "")
            por_nivel["alta"].append(es_correcta)
        elif nivel == "media":
            es_correcta = correcta in str(resultado["respuesta"] or "")
            por_nivel["media"].append(es_correcta)
        else:
            es_correcta = correcta in str(resultado["respuesta"] or "")
            por_nivel["baja"].append(es_correcta)
    
    calibracion = {}
    for nivel, resultados in por_nivel.items():
        if resultados:
            calibracion[nivel] = sum(resultados) / len(resultados)
    
    return calibracion

Ejercicios

Ejercicio 1: Implementar backward verification para ecuaciones

Para el problema "Resuelve 2x + 5 = 13", implementa backward verification que confirme que el valor de x encontrado satisface la ecuación.

Ver solución
from openai import OpenAI

client = OpenAI()

problema = "Resuelve la ecuación: 2x + 5 = 13"

# Paso 1: Resolver forward
r_forward = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": f"{problema}\nPiensa paso a paso. Al final escribe: x = [número]"}],
    temperature=0, max_tokens=300
)
output_forward = r_forward.choices[0].message.content

import re
match = re.search(r'x\s*=\s*([\d.]+)', output_forward)
x_propuesto = match.group(1) if match else "?"

print(f"Razonamiento forward:\n{output_forward}")
print(f"\nValor propuesto: x = {x_propuesto}")

# Paso 2: Backward verification
prompt_backward = f"""
Problema original: 2x + 5 = 13
Solución propuesta: x = {x_propuesto}

Verifica sustituyendo x en la ecuación:
Sustituye: 2({x_propuesto}) + 5 = ?
¿Es igual a 13?

Si sí: VERIFICADO ✓
Si no: ERROR ✗, el valor correcto es x = [corrección]
"""

r_backward = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": prompt_backward}],
    temperature=0, max_tokens=200
)
print(f"\nVerificación backward:\n{r_backward.choices[0].message.content}")
# x=4: 2(4)+5=13 ✓

Ejercicio 2: Confidence calibration

Ejecuta 10 problemas matemáticos con resolver_con_confidence. Para cada uno, registra el score de confianza y si la respuesta fue correcta. ¿Está bien calibrado el modelo?

Ver solución
from openai import OpenAI

client = OpenAI()

problemas_test = [
    ("5 + 3 = ?", "8"),
    ("17 × 23 = ?", "391"),
    ("Raíz cuadrada de 144 = ?", "12"),
    ("30% de 250 = ?", "75"),
    ("Integral de x² = ?", "x³/3"),
    ("1/3 + 1/4 = ?", "7/12"),
    ("2^10 = ?", "1024"),
    ("¿Qué es Pi?", "3.14"),
    ("Logaritmo natural de e = ?", "1"),
    ("Sen(90°) = ?", "1"),
]

resultados = []
for problema, correcta in problemas_test:
    r = resolver_con_confidence(problema)
    es_correcto = correcta in str(r["respuesta"] or "")
    
    resultados.append({
        "problema": problema,
        "respuesta": r["respuesta"],
        "correcta": correcta,
        "es_correcto": es_correcto,
        "confianza": r["confianza_score"],
        "nivel": r["confianza_nivel"]
    })
    
    print(f"{'✓' if es_correcto else '✗'} {problema[:30]:30} | Confianza: {r['confianza_score']} | {'OK' if es_correcto else 'ERROR'}")

# Calcular calibración
alta_confianza = [r for r in resultados if r["nivel"] in ["muy_alta", "alta"]]
accuracy_alta = sum(r["es_correcto"] for r in alta_confianza) / len(alta_confianza) if alta_confianza else 0

print(f"\nAltaConfianza → Accuracy real: {accuracy_alta:.0%}")
print("Modelo bien calibrado si accuracy_alta ≈ 0.90")

Ejercicio 3: Two-pass vs. single-pass en problemas con trampas

Implementa y compara ambos enfoques en problemas diseñados para engañar al modelo.

Ver solución
# Problemas "trampa" comunes para LLMs
PROBLEMAS_TRAMPA = [
    ("Un médico lleva 10 años de práctica. Comenzó a los 25 años. ¿Cuántos años tiene ahora?", "35"),
    ("Hay 23 estudiantes en clase. 1/3 son varones. ¿Cuántas son mujeres? (respuesta como número entero)", "15"),  # 23*2/3 ≈ 15.3 → 15
    ("Si hay 12 peces en un acuario y la mitad se mueren, ¿cuántos quedan?", "6"),
    ("Un padre tiene 3 hijos. El mayor tiene 10. El mediano tiene 8. La suma de las edades de los 3 hijos es 21. ¿Qué edad tiene el menor?", "3"),
]

print("Comparando single-pass vs. two-pass en problemas trampa:\n")

for problema, correcta in PROBLEMAS_TRAMPA:
    # Single pass
    r_single = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{problema}\n\nPiensa paso a paso."}],
        temperature=0, max_tokens=300
    )
    single_ok = correcta in r_single.choices[0].message.content
    
    # Two pass
    r_two = two_pass_verification(problema)
    two_ok = correcta in r_two["respuesta_final"]
    
    print(f"Problema: {problema[:60]}...")
    print(f"  Single-pass: {'✓' if single_ok else '✗'}")
    print(f"  Two-pass:    {'✓' if two_ok else '✗'}")
    print()

Ejercicio 4: Sistema de routing por confianza

Implementa un sistema que solo use two-pass verification cuando el confidence score sea bajo.

Ver solución
def resolver_con_routing_por_confianza(
    problema: str,
    umbral_confianza: float = 0.7
) -> dict:
    """
    Primero resuelve con single-pass y calcula confianza.
    Si la confianza es baja, escala a two-pass.
    """
    # Paso 1: Single-pass con confidence
    r_single = resolver_con_confidence(problema)
    
    if r_single["confianza_score"] and r_single["confianza_score"] >= umbral_confianza:
        return {
            "respuesta": r_single["respuesta"],
            "confianza": r_single["confianza_score"],
            "metodo_usado": "single_pass",
            "escalado": False
        }
    else:
        # Escalar a two-pass
        print(f"Confianza baja ({r_single['confianza_score']}), escalando a two-pass...")
        r_two = two_pass_verification(problema)
        return {
            "respuesta": r_two["respuesta_final"],
            "confianza": r_single["confianza_score"],  # Del primer pass
            "metodo_usado": "two_pass",
            "escalado": True,
            "veredicto_auditor": r_two["veredicto"]
        }


# Prueba
problemas = [
    "¿Cuánto es 5 + 3?",  # Confianza alta, no escala
    "¿Es consistente el siguiente argumento filosófico: El universo existe, por lo tanto Dios existe?",  # Confianza baja, escala
    "¿Cuánto es la integral de x³?",  # Confianza media
]

for p in problemas:
    r = resolver_con_routing_por_confianza(p)
    print(f"\nProblema: {p[:50]}...")
    print(f"Método: {r['metodo_usado']} | Escalado: {r['escalado']}")
    print(f"Respuesta: {r['respuesta']}")

Ejercicio 5: Detectar si el modelo fabricó el razonamiento

Implementa una función que detecte "reasoning fabricado" donde el modelo da pasos que no verifican numéricamente.

Ver solución
import re

def detectar_razonamiento_fabricado(output_cot: str) -> dict:
    """
    Detecta si el razonamiento CoT tiene operaciones matemáticas incorrectas.
    Extrae todas las ecuaciones del texto y las verifica.
    """
    errores = []
    operaciones_verificadas = 0
    
    # Buscar patrones como "17 × 23 = 391" o "340 + 51 = 391"
    patron_multiplicacion = r'(\d+(?:\.\d+)?)\s*[×x\*]\s*(\d+(?:\.\d+)?)\s*=\s*(\d+(?:\.\d+)?)'
    patron_suma = r'(\d+(?:\.\d+)?)\s*\+\s*(\d+(?:\.\d+)?)\s*=\s*(\d+(?:\.\d+)?)'
    patron_resta = r'(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*=\s*(\d+(?:\.\d+)?)'
    patron_division = r'(\d+(?:\.\d+)?)\s*[÷/]\s*(\d+(?:\.\d+)?)\s*=\s*(\d+(?:\.\d+)?)'
    
    def verificar_patron(patron, operacion_fn, nombre):
        nonlocal operaciones_verificadas
        for match in re.finditer(patron, output_cot):
            a, b, resultado_declarado = float(match.group(1)), float(match.group(2)), float(match.group(3))
            resultado_real = operacion_fn(a, b)
            operaciones_verificadas += 1
            
            if abs(resultado_real - resultado_declarado) > 0.01:
                errores.append(f"{nombre}: {a} op {b} = {resultado_declarado} (debería ser {resultado_real})")
    
    verificar_patron(patron_multiplicacion, lambda a, b: a * b, "Multiplicación")
    verificar_patron(patron_suma, lambda a, b: a + b, "Suma")
    verificar_patron(patron_resta, lambda a, b: a - b, "Resta")
    verificar_patron(patron_division, lambda a, b: a / b if b != 0 else float('inf'), "División")
    
    return {
        "operaciones_verificadas": operaciones_verificadas,
        "errores_encontrados": errores,
        "razonamiento_fabricado": len(errores) > 0,
        "tasa_error": len(errores) / max(operaciones_verificadas, 1)
    }


# Test
output_correcto = "17 × 20 = 340. 17 × 3 = 51. 340 + 51 = 391. Total: 391."
output_fabricado = "17 × 20 = 350. 17 × 3 = 51. 350 + 51 = 401. Total: 401."  # Error en primer paso

print("Correcto:", detectar_razonamiento_fabricado(output_correcto))
print("Fabricado:", detectar_razonamiento_fabricado(output_fabricado))

Resumen

  • Self-verification: El modelo revisa su propio trabajo; útil pero tiene bias de auto-confirmación
  • Backward check: Sustituye la respuesta en el problema original; muy efectivo para matemáticas
  • Constraint check: Verifica cada restricción con ✓/✗; esencial para problemas multi-condición
  • Confidence score: El modelo cuantifica su certeza; calibrar contra datos reales
  • Two-pass (auditor): Un segundo LLM independiente verifica; más robusto pero 2x costo
  • Ensemble: Combina Self-Consistency + Backward; máxima precisión pero 3-5x costo
  • Regla práctica: Para casos críticos, usar two-pass o ensemble. Para uso cotidiano, self-verification + backward es suficiente.

Recursos adicionales

  1. Self-Consistency Improves Chain of Thought Reasoning (Wang et al., 2022)
  2. Verify-and-Edit: A Knowledge-Enhanced CoT Framework (Zhao et al., 2023)
  3. Self-RAG: Learning to Retrieve, Generate, and Critique (Asai et al., 2023)
  4. Calibration of LLMs (Xiong et al., 2024)
  5. OpenAI API - Best Practices for Reliability