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ón | Costo (llamadas) | Efectividad | Mejor para |
|---|---|---|---|
| Sin verificación | 1x | Línea base | Prototipos, baja criticidad |
| Self-verification | 1x (más tokens) | +10-15% | Balance costo/beneficio |
| Backward check | 1x (más tokens) | +20-30% | Matemáticas, ecuaciones |
| Constraint check | 1x (más tokens) | +25-35% | Problemas multi-restricción |
| Confidence score | 1x (más tokens) | Informacional | Monitoreo de calidad |
| Two-pass (auditor) | 2x | +30-40% | Aplicaciones críticas |
| Ensemble | 3-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
- Self-Consistency Improves Chain of Thought Reasoning (Wang et al., 2022)
- Verify-and-Edit: A Knowledge-Enhanced CoT Framework (Zhao et al., 2023)
- Self-RAG: Learning to Retrieve, Generate, and Critique (Asai et al., 2023)
- Calibration of LLMs (Xiong et al., 2024)
- OpenAI API - Best Practices for Reliability