Módulo 4: Chain-of-Thought y Razonamiento
8. Proyecto: Reasoning Engine con CoT Verificable
Descripción
Construye un motor de razonamiento completo que resuelve problemas de matemáticas y lógica usando CoT, verifica cada paso, reporta un score de confianza, y puede comparar su desempeño con y sin CoT. Este proyecto integra todo lo aprendido en el módulo: Zero-Shot CoT, Manual CoT, Verification Patterns, y los principios de cuándo no usar CoT.
Tiempo estimado: 120-180 minutos
Lo que construirás:
- Clase
ReasoningEnginecon soporte para múltiples dominios - Sistema de verificación multi-paso (backward check + constraint verification)
- Confidence scoring calibrado
- Benchmark de comparación CoT vs. no-CoT
- CLI para uso interactivo
Especificaciones del Proyecto
Inputs
- Texto libre: problemas de matemáticas o lógica
- Tipo de problema opcional:
math,logic,general - Modo de verificación:
none,self,backward,two_pass
Outputs
{
"problema": "...",
"tipo": "math",
"razonamiento": "Paso 1: ...\nPaso 2: ...",
"respuesta": "391",
"verificacion": {
"metodo": "backward",
"resultado": "VERIFICADO",
"nota": "391/17=23 ✓"
},
"confianza": {
"score": 0.95,
"nivel": "MUY_ALTA",
"factores": {
"certeza_proceso": 1.0,
"verificabilidad": 1.0,
"claridad_problema": 0.9,
"conocimiento_dominio": 0.9
}
},
"tokens_usados": 342,
"tiempo_ms": 1240
}
Estructura del Proyecto
reasoning_engine/
├── engine.py # Clase principal ReasoningEngine
├── verifiers.py # Módulo de verificación
├── parsers.py # Extracción de respuestas del CoT
├── prompts.py # Templates de prompts por dominio
├── benchmark.py # Sistema de benchmark
├── cli.py # Interface de línea de comandos
└── problems.py # Dataset de problemas de prueba
Implementación: prompts.py
# prompts.py
"""Templates de prompts CoT por dominio."""
MATH_COT_PROMPT = """Resuelve el problema matemático paso a paso.
Para cada paso:
1. Describe la operación
2. Muestra el cálculo
3. Escribe el resultado intermedio
Al final, escribe EXACTAMENTE:
RESPUESTA: [número o expresión]
CONFIANZA: [número entre 0 y 1]
Problema: {problema}
"""
LOGIC_COT_PROMPT = """Analiza el argumento lógico paso a paso.
1. Identifica las PREMISAS (lista numerada)
2. Identifica la CONCLUSIÓN que se quiere evaluar
3. Determina la forma lógica (modus ponens, modus tollens, silogismo, etc.)
4. Verifica si la conclusión se sigue de las premisas
5. Busca contraejemplos si sospechas que es inválido
Al final, escribe EXACTAMENTE:
RESPUESTA: VÁLIDO / INVÁLIDO
RAZONAMIENTO: [una línea explicando por qué]
CONFIANZA: [número entre 0 y 1]
Argumento: {problema}
"""
GENERAL_COT_PROMPT = """Resuelve el problema paso a paso.
Analiza cuidadosamente, muestra tu razonamiento, y llega a una conclusión clara.
Al final, escribe EXACTAMENTE:
RESPUESTA: [tu respuesta]
CONFIANZA: [número entre 0 y 1, donde 1 = certeza total]
Problema: {problema}
"""
DIRECT_PROMPT = """Responde directamente y de forma concisa.
Problema: {problema}
Respuesta:"""
VERIFICATION_BACKWARD_MATH = """Verifica que esta respuesta sea correcta sustituyendo hacia atrás.
Problema original: {problema}
Respuesta propuesta: {respuesta}
Pasos de verificación:
1. Toma el valor propuesto
2. Aplícalo al problema original
3. Verifica que se cumplan todas las condiciones
Escribe:
ESTADO: VERIFICADO / ERROR
DETALLES: [qué verificaste y cómo]
RESPUESTA_CORREGIDA: [solo si hubo error, la respuesta correcta]
"""
VERIFICATION_LOGIC = """Verifica la conclusión del argumento lógico.
Problema original: {problema}
Conclusión propuesta: {respuesta}
Para verificar:
1. ¿La conclusión sigue de las premisas por alguna regla válida?
2. ¿Hay algún contraejemplo donde las premisas sean verdaderas y la conclusión falsa?
Escribe:
ESTADO: VERIFICADO / ERROR
DETALLES: [regla de inferencia usada o contraejemplo encontrado]
"""
Implementación: parsers.py
# parsers.py
"""Módulo para extraer respuestas de outputs CoT."""
import re
from dataclasses import dataclass
@dataclass
class ParsedOutput:
respuesta: str | None
confianza: float | None
razonamiento: str
parse_exitoso: bool
metodo_extraccion: str
def parse_respuesta_matematica(output: str) -> ParsedOutput:
"""
Extrae respuesta numérica y confianza de un output CoT matemático.
Intenta múltiples estrategias en orden de confiabilidad.
"""
razonamiento = output
# Estrategia 1: Buscar etiqueta explícita RESPUESTA:
match_resp = re.search(
r'^RESPUESTA:\s*([\d,.$€%\.±\-\+\/]+)',
output,
re.MULTILINE | re.IGNORECASE
)
if match_resp:
respuesta = match_resp.group(1).strip().replace(',', '')
# Buscar confianza
match_conf = re.search(
r'^CONFIANZA:\s*(0\.\d+|1\.0|1)',
output,
re.MULTILINE | re.IGNORECASE
)
confianza = float(match_conf.group(1)) if match_conf else None
return ParsedOutput(
respuesta=respuesta,
confianza=confianza,
razonamiento=output,
parse_exitoso=True,
metodo_extraccion="etiqueta_explícita"
)
# Estrategia 2: Buscar frases de conclusión
patrones_conclusion = [
r'(?:por lo tanto|therefore|en conclusión|la respuesta es)[,:\s]+\$?([\d,\.]+)',
r'(?:total|resultado|suma)[:\s]+\$?([\d,\.]+)',
r'=\s*\$?([\d,\.]+)\s*$',
]
for patron in patrones_conclusion:
match = re.search(patron, output.lower())
if match:
return ParsedOutput(
respuesta=match.group(1).replace(',', ''),
confianza=None,
razonamiento=output,
parse_exitoso=True,
metodo_extraccion="patron_conclusion"
)
# Estrategia 3: Último número del texto (fallback)
numeros = re.findall(r'\b\d+(?:\.\d+)?\b', output)
if numeros:
return ParsedOutput(
respuesta=numeros[-1],
confianza=None,
razonamiento=output,
parse_exitoso=True,
metodo_extraccion="ultimo_numero_fallback"
)
return ParsedOutput(
respuesta=None,
confianza=None,
razonamiento=output,
parse_exitoso=False,
metodo_extraccion="ninguno"
)
def parse_respuesta_logica(output: str) -> ParsedOutput:
"""Extrae veredicto VÁLIDO/INVÁLIDO de un output CoT lógico."""
# Buscar etiqueta explícita
match = re.search(
r'^RESPUESTA:\s*(VÁLIDO|INVÁLIDO)',
output,
re.MULTILINE | re.IGNORECASE
)
if match:
confianza_match = re.search(
r'^CONFIANZA:\s*(0\.\d+|1\.0)',
output,
re.MULTILINE | re.IGNORECASE
)
return ParsedOutput(
respuesta=match.group(1).upper(),
confianza=float(confianza_match.group(1)) if confianza_match else None,
razonamiento=output,
parse_exitoso=True,
metodo_extraccion="etiqueta_explícita"
)
# Búsqueda en la parte final del texto
ultima_parte = output[-400:].upper()
if "INVÁLIDO" in ultima_parte:
return ParsedOutput(
respuesta="INVÁLIDO",
confianza=None,
razonamiento=output,
parse_exitoso=True,
metodo_extraccion="busqueda_texto"
)
elif "VÁLIDO" in ultima_parte:
return ParsedOutput(
respuesta="VÁLIDO",
confianza=None,
razonamiento=output,
parse_exitoso=True,
metodo_extraccion="busqueda_texto"
)
return ParsedOutput(
respuesta=None,
confianza=None,
razonamiento=output,
parse_exitoso=False,
metodo_extraccion="ninguno"
)
Implementación: verifiers.py
# verifiers.py
"""Sistema de verificación de respuestas CoT."""
from openai import OpenAI
from dataclasses import dataclass
client = OpenAI()
@dataclass
class VerificationResult:
metodo: str
estado: str # "VERIFICADO", "ERROR", "INDETERMINADO"
detalles: str
respuesta_corregida: str | None = None
confianza_verificacion: float | None = None
def verificar_backward_matematico(
problema: str,
respuesta: str
) -> VerificationResult:
"""
Verifica una respuesta matemática sustituyendo hacia atrás.
"""
from prompts import VERIFICATION_BACKWARD_MATH
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": VERIFICATION_BACKWARD_MATH.format(
problema=problema,
respuesta=respuesta
)
}],
temperature=0,
max_tokens=300
)
output = response.choices[0].message.content
import re
estado_match = re.search(r'ESTADO:\s*(VERIFICADO|ERROR)', output, re.IGNORECASE)
correccion_match = re.search(r'RESPUESTA_CORREGIDA:\s*(.+?)(?:\n|$)', output, re.IGNORECASE)
estado = estado_match.group(1).upper() if estado_match else "INDETERMINADO"
return VerificationResult(
metodo="backward_matematico",
estado=estado,
detalles=output,
respuesta_corregida=correccion_match.group(1).strip() if correccion_match and estado == "ERROR" else None,
confianza_verificacion=1.0 if estado == "VERIFICADO" else 0.3 if estado == "ERROR" else 0.5
)
def verificar_logica(
problema: str,
respuesta: str
) -> VerificationResult:
"""
Verifica un razonamiento lógico buscando contraejemplos.
"""
from prompts import VERIFICATION_LOGIC
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{
"role": "user",
"content": VERIFICATION_LOGIC.format(
problema=problema,
respuesta=respuesta
)
}],
temperature=0,
max_tokens=300
)
output = response.choices[0].message.content
import re
estado_match = re.search(r'ESTADO:\s*(VERIFICADO|ERROR)', output, re.IGNORECASE)
estado = estado_match.group(1).upper() if estado_match else "INDETERMINADO"
return VerificationResult(
metodo="verificacion_logica",
estado=estado,
detalles=output,
confianza_verificacion=1.0 if estado == "VERIFICADO" else 0.3
)
def calcular_confidence_score(
parsed_output, # ParsedOutput
verification_result: VerificationResult,
tipo_problema: str
) -> dict:
"""
Calcula el score de confianza final combinando CoT confidence + verificación.
"""
# Score base del CoT (si el modelo lo proporcionó)
cot_confidence = parsed_output.confianza or 0.7 # Default 0.7 si no hay score
# Ajuste por verificación
if verification_result.estado == "VERIFICADO":
verificacion_factor = 1.0
elif verification_result.estado == "INDETERMINADO":
verificacion_factor = 0.8
else: # ERROR
verificacion_factor = 0.2
# Ajuste por método de extracción
parse_factor = {
"etiqueta_explícita": 1.0,
"patron_conclusion": 0.9,
"ultimo_numero_fallback": 0.7,
"ninguno": 0.1
}.get(parsed_output.metodo_extraccion, 0.7)
# Score final ponderado
score_final = cot_confidence * 0.4 + verificacion_factor * 0.4 + parse_factor * 0.2
score_final = min(1.0, max(0.0, score_final)) # Clamp a [0, 1]
nivel = (
"MUY_ALTA" if score_final >= 0.9 else
"ALTA" if score_final >= 0.7 else
"MEDIA" if score_final >= 0.5 else
"BAJA"
)
return {
"score": round(score_final, 3),
"nivel": nivel,
"factores": {
"cot_confidence": cot_confidence,
"verificacion_factor": verificacion_factor,
"parse_factor": parse_factor
}
}
Implementación Principal: engine.py
# engine.py
"""Motor de razonamiento principal con CoT verificable."""
import time
import re
from dataclasses import dataclass, field
from enum import Enum
from openai import OpenAI
# Importar módulos del proyecto
# (En un archivo real, importar desde los módulos correspondientes)
client = OpenAI()
class TipoProblema(Enum):
MATH = "math"
LOGIC = "logic"
GENERAL = "general"
class ModoVerificacion(Enum):
NONE = "none"
SELF = "self"
BACKWARD = "backward"
TWO_PASS = "two_pass"
@dataclass
class VerificationInfo:
metodo: str
estado: str
detalles: str
respuesta_corregida: str | None = None
@dataclass
class ResultadoReasoning:
problema: str
tipo: TipoProblema
razonamiento: str
respuesta: str | None
verificacion: VerificationInfo | None
confianza: dict
tokens_usados: int
tiempo_ms: int
modo_cot: str
parse_exitoso: bool
class ReasoningEngine:
"""
Motor de razonamiento con CoT verificable.
Soporta múltiples tipos de problemas, modos de verificación,
y genera scores de confianza calibrados.
Ejemplo de uso:
engine = ReasoningEngine()
resultado = engine.resolver("¿Cuánto es 17 × 23?")
print(resultado.respuesta) # "391"
print(resultado.confianza["nivel"]) # "MUY_ALTA"
"""
PROMPTS = {
TipoProblema.MATH: """Resuelve el problema matemático paso a paso.
Para cada paso:
1. Describe la operación
2. Muestra el cálculo
3. Escribe el resultado intermedio
Al final, escribe EXACTAMENTE:
RESPUESTA: [número o expresión]
CONFIANZA: [número entre 0 y 1]
Problema: {problema}
""",
TipoProblema.LOGIC: """Analiza el argumento lógico paso a paso.
1. Lista las PREMISAS
2. Identifica la CONCLUSIÓN
3. Determina si la conclusión se sigue necesariamente
4. Busca contraejemplos si sospechas que es inválido
Al final, escribe EXACTAMENTE:
RESPUESTA: VÁLIDO / INVÁLIDO
RAZONAMIENTO: [una línea]
CONFIANZA: [número entre 0 y 1]
Argumento: {problema}
""",
TipoProblema.GENERAL: """Resuelve paso a paso.
Al final, escribe EXACTAMENTE:
RESPUESTA: [tu respuesta]
CONFIANZA: [número entre 0 y 1]
Problema: {problema}
"""
}
def __init__(
self,
model: str = "gpt-4o-mini",
temperatura: float = 0.0,
max_tokens: int = 700
):
self.model = model
self.temperatura = temperatura
self.max_tokens = max_tokens
def detectar_tipo(self, problema: str) -> TipoProblema:
"""Detecta automáticamente el tipo de problema."""
problema_lower = problema.lower()
keywords_math = [
"cuánto", "calcula", "resuelve", "¿cuántos", "suma", "resta",
"multiplica", "divide", "porcentaje", "descuento", "precio",
"ecuación", "álgebra", "integral", "derivada", "+", "-", "×"
]
keywords_logic = [
"si...entonces", "por lo tanto", "implica", "válido", "inválido",
"premisa", "conclusión", "todos los", "algunos", "ningún",
"lógica", "argumento", "¿se sigue"
]
score_math = sum(1 for k in keywords_math if k in problema_lower)
score_logic = sum(1 for k in keywords_logic if k in problema_lower)
if score_math > score_logic:
return TipoProblema.MATH
elif score_logic > 0:
return TipoProblema.LOGIC
else:
return TipoProblema.GENERAL
def _construir_prompt_cot(self, problema: str, tipo: TipoProblema) -> str:
"""Construye el prompt CoT apropiado para el tipo de problema."""
template = self.PROMPTS[tipo]
return template.format(problema=problema)
def _parsear_output(self, output: str, tipo: TipoProblema) -> tuple[str | None, float | None, bool]:
"""
Extrae respuesta y confianza del output CoT.
Returns:
tuple de (respuesta, confianza, parse_exitoso)
"""
# Buscar etiqueta RESPUESTA:
match_resp = re.search(
r'^RESPUESTA:\s*(.+?)$',
output,
re.MULTILINE | re.IGNORECASE
)
# Buscar etiqueta CONFIANZA:
match_conf = re.search(
r'^CONFIANZA:\s*(0\.\d+|1\.0|1)',
output,
re.MULTILINE | re.IGNORECASE
)
respuesta = None
confianza = None
parse_exitoso = False
if match_resp:
respuesta = match_resp.group(1).strip()
parse_exitoso = True
else:
# Fallback: buscar patrones de conclusión
if tipo == TipoProblema.MATH:
numeros = re.findall(r'\b\d+(?:\.\d+)?\b', output)
respuesta = numeros[-1] if numeros else None
parse_exitoso = respuesta is not None
elif tipo == TipoProblema.LOGIC:
ultima = output[-300:].upper()
if "INVÁLIDO" in ultima:
respuesta = "INVÁLIDO"
parse_exitoso = True
elif "VÁLIDO" in ultima:
respuesta = "VÁLIDO"
parse_exitoso = True
if match_conf:
try:
confianza = float(match_conf.group(1))
except ValueError:
confianza = None
return respuesta, confianza, parse_exitoso
def _verificar_respuesta(
self,
problema: str,
respuesta: str,
tipo: TipoProblema,
modo: ModoVerificacion
) -> VerificationInfo | None:
"""Verifica la respuesta según el modo seleccionado."""
if modo == ModoVerificacion.NONE:
return None
if modo == ModoVerificacion.SELF:
prompt = f"""Verifica si esta respuesta es correcta.
Problema: {problema}
Respuesta propuesta: {respuesta}
¿Es correcta? Responde VERIFICADO o ERROR. Explica brevemente."""
r = client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0, max_tokens=200
)
output_v = r.choices[0].message.content
estado = "VERIFICADO" if "verificado" in output_v.lower() else "INDETERMINADO"
return VerificationInfo("self_verification", estado, output_v)
elif modo == ModoVerificacion.BACKWARD:
if tipo == TipoProblema.MATH:
prompt = f"""Verifica sustituyendo la respuesta en el problema original.
Problema: {problema}
Respuesta: {respuesta}
Sustituye y verifica. Escribe VERIFICADO ✓ o ERROR ✗ + corrección si aplica."""
else:
prompt = f"""Verifica este argumento lógico buscando contraejemplos.
Argumento: {problema}
Conclusión: {respuesta}
¿Hay contraejemplo? Escribe VERIFICADO ✓ o ERROR ✗."""
r = client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0, max_tokens=300
)
output_v = r.choices[0].message.content
if "✓" in output_v or "verificado" in output_v.lower():
estado = "VERIFICADO"
elif "✗" in output_v or "error" in output_v.lower():
estado = "ERROR"
# Intentar extraer corrección
match_corr = re.search(r'(?:corrección|correcta)[:\s]+(.+?)(?:\n|$)', output_v, re.IGNORECASE)
correccion = match_corr.group(1).strip() if match_corr else None
return VerificationInfo("backward", estado, output_v, correccion)
else:
estado = "INDETERMINADO"
return VerificationInfo("backward", estado, output_v)
elif modo == ModoVerificacion.TWO_PASS:
prompt = f"""Eres un auditor independiente. Resuelve el problema TÚ MISMO
y verifica si la solución propuesta es correcta.
Problema: {problema}
Solución propuesta: {respuesta}
Tu solución independiente:
[razona aquí]
Veredicto: CORRECTO o INCORRECTO
Si INCORRECTO: SOLUCIÓN_CORRECTA: [respuesta]"""
r = client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0, max_tokens=400
)
output_v = r.choices[0].message.content
if "correcto" in output_v.lower() and "incorrecto" not in output_v.lower():
estado = "VERIFICADO"
elif "incorrecto" in output_v.lower():
estado = "ERROR"
match_sol = re.search(r'SOLUCIÓN_CORRECTA:\s*(.+?)(?:\n|$)', output_v, re.IGNORECASE)
correccion = match_sol.group(1).strip() if match_sol else None
return VerificationInfo("two_pass", estado, output_v, correccion)
else:
estado = "INDETERMINADO"
return VerificationInfo("two_pass", estado, output_v)
return None
def _calcular_confianza(
self,
confianza_cot: float | None,
verificacion: VerificationInfo | None,
parse_exitoso: bool
) -> dict:
"""Calcula el score de confianza final."""
base = confianza_cot or 0.65
verificacion_factor = 1.0
if verificacion:
if verificacion.estado == "VERIFICADO":
verificacion_factor = 1.0
elif verificacion.estado == "INDETERMINADO":
verificacion_factor = 0.8
else: # ERROR
verificacion_factor = 0.2
parse_factor = 1.0 if parse_exitoso else 0.4
score = base * 0.4 + verificacion_factor * 0.4 + parse_factor * 0.2
score = round(min(1.0, max(0.0, score)), 3)
nivel = (
"MUY_ALTA" if score >= 0.9 else
"ALTA" if score >= 0.7 else
"MEDIA" if score >= 0.5 else
"BAJA"
)
return {
"score": score,
"nivel": nivel,
"factores": {
"cot_confidence": base,
"verificacion": verificacion_factor,
"parse": parse_factor
}
}
def resolver(
self,
problema: str,
tipo: TipoProblema | None = None,
verificacion: ModoVerificacion = ModoVerificacion.BACKWARD,
usar_cot: bool = True
) -> ResultadoReasoning:
"""
Resuelve un problema con CoT y verificación opcionales.
Args:
problema: El problema a resolver
tipo: Tipo de problema (detecta automáticamente si None)
verificacion: Modo de verificación a usar
usar_cot: Si False, responde directamente sin CoT
Returns:
ResultadoReasoning con todos los detalles
"""
t_inicio = time.time()
# Detectar tipo si no se especificó
tipo_detectado = tipo or self.detectar_tipo(problema)
# Construir prompt
if usar_cot:
prompt = self._construir_prompt_cot(problema, tipo_detectado)
modo_cot = "chain_of_thought"
else:
prompt = f"Responde directamente: {problema}"
modo_cot = "directo"
# Llamar al modelo
response = client.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=self.temperatura,
max_tokens=self.max_tokens
)
output = response.choices[0].message.content
tokens = response.usage.total_tokens
# Parsear respuesta
respuesta, confianza_cot, parse_ok = self._parsear_output(output, tipo_detectado)
# Verificar si CoT está activo y hay respuesta
verificacion_result = None
tokens_verificacion = 0
if usar_cot and respuesta and verificacion != ModoVerificacion.NONE:
verificacion_result = self._verificar_respuesta(
problema, respuesta, tipo_detectado, verificacion
)
# Usar respuesta corregida si la verificación encontró error
if verificacion_result and verificacion_result.respuesta_corregida:
respuesta = verificacion_result.respuesta_corregida
# Calcular confianza final
confianza_info = self._calcular_confianza(confianza_cot, verificacion_result, parse_ok)
tiempo_ms = int((time.time() - t_inicio) * 1000)
return ResultadoReasoning(
problema=problema,
tipo=tipo_detectado,
razonamiento=output,
respuesta=respuesta,
verificacion=verificacion_result,
confianza=confianza_info,
tokens_usados=tokens + tokens_verificacion,
tiempo_ms=tiempo_ms,
modo_cot=modo_cot,
parse_exitoso=parse_ok
)
Implementación: benchmark.py
# benchmark.py
"""Sistema de benchmark para comparar CoT vs. no-CoT."""
from dataclasses import dataclass
from engine import ReasoningEngine, TipoProblema, ModoVerificacion
@dataclass
class ResultadoBenchmark:
total_problemas: int
correctos_cot: int
correctos_sin_cot: int
accuracy_cot: float
accuracy_sin_cot: float
mejora_accuracy: float
tokens_promedio_cot: float
tokens_promedio_sin_cot: float
multiplicador_tokens: float
tiempo_promedio_cot_ms: float
tiempo_promedio_sin_cot_ms: float
# Dataset de problemas con respuestas conocidas
PROBLEMAS_MATH = [
# (problema, respuesta_correcta)
("¿Cuánto es 17 × 23?", "391"),
("Un producto cuesta $80 con descuento del 20%. ¿Cuál es el precio original?", "100"),
("Si A trabaja en 6h y B en 4h, ¿cuánto tardan juntos?", "2.4"),
("¿Cuánto es 15% de $240?", "36"),
("Tren A sale a 80 km/h. Tren B sale 30 min después a 120 km/h. ¿Cuándo alcanza B a A?", "1"),
("¿Cuántos enteros del 1 al 50 son divisibles por 3 o por 5?", "23"),
("Invierto $1000 al 5% anual compuesto por 2 años. ¿Cuánto tengo?", "1102.5"),
("La hipotenusa de un triángulo rectángulo con catetos 6 y 8 es:", "10"),
]
PROBLEMAS_LOGIC = [
# (problema, respuesta_correcta)
("Si P entonces Q. P es verdadero. ¿Q es verdadero?", "VÁLIDO"),
("Si llueve, el suelo se moja. El suelo está mojado. ¿Llueve?", "INVÁLIDO"),
("Todos los gatos son animales. Misi es un gato. ¿Misi es animal?", "VÁLIDO"),
("Algunos perros ladran. Rex es un perro. ¿Rex ladra?", "INVÁLIDO"),
("Si no estudio, suspendo. No suspendo. ¿Estudié?", "VÁLIDO"),
]
def ejecutar_benchmark(
n_problemas_math: int = 5,
n_problemas_logic: int = 3,
modo_verificacion: ModoVerificacion = ModoVerificacion.BACKWARD
) -> ResultadoBenchmark:
"""
Ejecuta benchmark completo de CoT vs. no-CoT.
Args:
n_problemas_math: Número de problemas de matemáticas a usar
n_problemas_logic: Número de problemas de lógica a usar
modo_verificacion: Modo de verificación para el motor CoT
Returns:
ResultadoBenchmark con estadísticas detalladas
"""
engine = ReasoningEngine()
todos_problemas = (
[(p, r, TipoProblema.MATH) for p, r in PROBLEMAS_MATH[:n_problemas_math]] +
[(p, r, TipoProblema.LOGIC) for p, r in PROBLEMAS_LOGIC[:n_problemas_logic]]
)
correctos_cot = 0
correctos_sin_cot = 0
tokens_cot_total = 0
tokens_sin_cot_total = 0
tiempos_cot = []
tiempos_sin_cot = []
print(f"\n{'='*70}")
print(f"BENCHMARK: CoT vs. Sin CoT ({len(todos_problemas)} problemas)")
print(f"{'='*70}")
for problema, respuesta_correcta, tipo in todos_problemas:
print(f"\n► {problema[:60]}...")
# Con CoT
r_cot = engine.resolver(
problema,
tipo=tipo,
verificacion=modo_verificacion,
usar_cot=True
)
# Sin CoT
r_sin = engine.resolver(
problema,
tipo=tipo,
verificacion=ModoVerificacion.NONE,
usar_cot=False
)
# Verificar si son correctas
cot_ok = respuesta_correcta in str(r_cot.respuesta or "")
sin_ok = respuesta_correcta in str(r_sin.respuesta or "")
if cot_ok:
correctos_cot += 1
if sin_ok:
correctos_sin_cot += 1
tokens_cot_total += r_cot.tokens_usados
tokens_sin_cot_total += r_sin.tokens_usados
tiempos_cot.append(r_cot.tiempo_ms)
tiempos_sin_cot.append(r_sin.tiempo_ms)
print(f" Con CoT: {'✓' if cot_ok else '✗'} | Resp: {r_cot.respuesta} | Confianza: {r_cot.confianza['nivel']} | {r_cot.tokens_usados} tokens")
print(f" Sin CoT: {'✓' if sin_ok else '✗'} | Resp: {r_sin.respuesta} | {r_sin.tokens_usados} tokens")
n = len(todos_problemas)
acc_cot = correctos_cot / n
acc_sin = correctos_sin_cot / n
resultado = ResultadoBenchmark(
total_problemas=n,
correctos_cot=correctos_cot,
correctos_sin_cot=correctos_sin_cot,
accuracy_cot=acc_cot,
accuracy_sin_cot=acc_sin,
mejora_accuracy=acc_cot - acc_sin,
tokens_promedio_cot=tokens_cot_total / n,
tokens_promedio_sin_cot=tokens_sin_cot_total / n,
multiplicador_tokens=(tokens_cot_total / n) / max(tokens_sin_cot_total / n, 1),
tiempo_promedio_cot_ms=sum(tiempos_cot) / n,
tiempo_promedio_sin_cot_ms=sum(tiempos_sin_cot) / n
)
print(f"\n{'='*70}")
print("RESUMEN BENCHMARK")
print(f"{'='*70}")
print(f"Accuracy con CoT: {acc_cot:.1%} ({correctos_cot}/{n})")
print(f"Accuracy sin CoT: {acc_sin:.1%} ({correctos_sin_cot}/{n})")
print(f"Mejora de CoT: +{resultado.mejora_accuracy:.1%}")
print(f"Tokens con CoT: {resultado.tokens_promedio_cot:.0f} (promedio)")
print(f"Tokens sin CoT: {resultado.tokens_promedio_sin_cot:.0f} (promedio)")
print(f"Multiplicador CoT: {resultado.multiplicador_tokens:.1f}x más tokens")
return resultado
Implementación: cli.py
# cli.py
"""Interface de línea de comandos para el Reasoning Engine."""
import sys
from engine import ReasoningEngine, TipoProblema, ModoVerificacion
def main():
"""CLI interactivo para el Reasoning Engine."""
engine = ReasoningEngine()
print("\n" + "="*60)
print("REASONING ENGINE - CoT Verificable")
print("="*60)
print("Comandos:")
print(" [problema] → Resolver con configuración por defecto")
print(" :tipo math → Establecer tipo (math/logic/general)")
print(" :verificacion backward → Modo (none/self/backward/two_pass)")
print(" :benchmark → Ejecutar benchmark completo")
print(" :salir → Salir")
print("="*60)
tipo_actual = None
verificacion_actual = ModoVerificacion.BACKWARD
while True:
try:
entrada = input("\n> ").strip()
except (KeyboardInterrupt, EOFError):
print("\n¡Hasta luego!")
sys.exit(0)
if not entrada:
continue
if entrada.lower() == ":salir":
print("¡Hasta luego!")
break
elif entrada.lower().startswith(":tipo "):
tipo_str = entrada.split(" ", 1)[1].strip()
mapa = {"math": TipoProblema.MATH, "logic": TipoProblema.LOGIC, "general": TipoProblema.GENERAL}
if tipo_str in mapa:
tipo_actual = mapa[tipo_str]
print(f"Tipo establecido: {tipo_actual.value}")
else:
print(f"Tipo no reconocido. Opciones: {list(mapa.keys())}")
elif entrada.lower().startswith(":verificacion "):
modo_str = entrada.split(" ", 1)[1].strip()
mapa_v = {
"none": ModoVerificacion.NONE,
"self": ModoVerificacion.SELF,
"backward": ModoVerificacion.BACKWARD,
"two_pass": ModoVerificacion.TWO_PASS
}
if modo_str in mapa_v:
verificacion_actual = mapa_v[modo_str]
print(f"Verificación: {verificacion_actual.value}")
else:
print(f"Modo no reconocido. Opciones: {list(mapa_v.keys())}")
elif entrada.lower() == ":benchmark":
from benchmark import ejecutar_benchmark
ejecutar_benchmark(n_problemas_math=5, n_problemas_logic=3)
else:
# Resolver el problema
print(f"\n[Resolviendo con CoT + verificación {verificacion_actual.value}...]")
resultado = engine.resolver(
entrada,
tipo=tipo_actual,
verificacion=verificacion_actual
)
print(f"\n── RAZONAMIENTO ──")
print(resultado.razonamiento)
print(f"\n── RESULTADO ──")
print(f"Tipo detectado: {resultado.tipo.value}")
print(f"Respuesta: {resultado.respuesta}")
print(f"Confianza: {resultado.confianza['score']} ({resultado.confianza['nivel']})")
if resultado.verificacion:
estado_emoji = "✓" if resultado.verificacion.estado == "VERIFICADO" else "✗" if resultado.verificacion.estado == "ERROR" else "?"
print(f"Verificación: {estado_emoji} {resultado.verificacion.estado} ({resultado.verificacion.metodo})")
print(f"Tokens usados: {resultado.tokens_usados}")
print(f"Tiempo: {resultado.tiempo_ms}ms")
if __name__ == "__main__":
main()
Ejecución y Demo
# demo.py - Ejecutar para ver el engine en acción
from engine import ReasoningEngine, TipoProblema, ModoVerificacion
from benchmark import ejecutar_benchmark
def demo_basico():
"""Demuestra el uso básico del Reasoning Engine."""
engine = ReasoningEngine()
print("=" * 60)
print("DEMO: Reasoning Engine CoT Verificable")
print("=" * 60)
# Problema matemático
print("\n1. PROBLEMA MATEMÁTICO")
resultado = engine.resolver(
"Si invierto $5,000 al 8% anual compuesto durante 3 años, ¿cuánto tengo?",
tipo=TipoProblema.MATH,
verificacion=ModoVerificacion.BACKWARD
)
print(f"Respuesta: {resultado.respuesta}")
print(f"Confianza: {resultado.confianza['score']} ({resultado.confianza['nivel']})")
verificacion_estado = resultado.verificacion.estado if resultado.verificacion else "N/A"
print(f"Verificación: {verificacion_estado}")
# Respuesta esperada: $6,298.56 (aproximado)
# Problema lógico
print("\n2. PROBLEMA LÓGICO")
resultado_logico = engine.resolver(
"Si estudias, pasas el examen. No pasaste el examen. ¿Estudiaste?",
tipo=TipoProblema.LOGIC,
verificacion=ModoVerificacion.BACKWARD
)
print(f"Respuesta: {resultado_logico.respuesta}")
print(f"Confianza: {resultado_logico.confianza['score']}")
# Respuesta esperada: VÁLIDO (modus tollens)
# Comparación CoT vs. sin CoT
print("\n3. COMPARACIÓN CoT vs. Sin CoT")
problema_test = "¿Cuánto es 23% de $450?"
r_cot = engine.resolver(problema_test, usar_cot=True, verificacion=ModoVerificacion.NONE)
r_sin = engine.resolver(problema_test, usar_cot=False, verificacion=ModoVerificacion.NONE)
print(f"Con CoT: {r_cot.respuesta} ({r_cot.tokens_usados} tokens)")
print(f"Sin CoT: {r_sin.respuesta} ({r_sin.tokens_usados} tokens)")
print(f"Respuesta esperada: 103.5")
def demo_benchmark():
"""Ejecuta benchmark completo."""
resultados = ejecutar_benchmark(
n_problemas_math=5,
n_problemas_logic=3,
modo_verificacion=ModoVerificacion.BACKWARD
)
print(f"\nCONCLUSIÓN:")
if resultados.mejora_accuracy > 0.1:
print(f"CoT mejora significativamente la accuracy (+{resultados.mejora_accuracy:.0%})")
print(f"El costo adicional ({resultados.multiplicador_tokens:.1f}x tokens) está justificado para este tipo de problemas.")
elif resultados.mejora_accuracy > 0:
print(f"CoT mejora modestamente la accuracy (+{resultados.mejora_accuracy:.0%})")
print(f"Considerar si el costo extra vale la pena según el caso de uso.")
else:
print(f"CoT no mejoró la accuracy en este benchmark.")
print(f"Revisar si los problemas son apropiados para CoT.")
if __name__ == "__main__":
print("Ejecutando demo básico...")
demo_basico()
print("\n\nEjecutando benchmark...")
demo_benchmark()
Criterios de Éxito del Proyecto
Marca cada criterio al completarlo:
- Resolución básica: El engine resuelve correctamente 8/10 problemas matemáticos simples
- Tipos de problema: Detecta automáticamente el tipo (math/logic) con ≥80% accuracy
- Verificación backward: La verificación detecta errores y corrige la respuesta cuando es posible
- Confidence score: El score refleja la dificultad real (problemas simples → alta confianza)
- Benchmark: Muestra mejora medible de CoT vs. sin CoT en los problemas del dataset
- CLI funcional: El CLI acepta problemas en texto libre y muestra resultados formateados
- Manejo de errores: El engine no falla si la API no responde o si no puede parsear la respuesta
- Extensibilidad: Es fácil añadir un nuevo tipo de problema (ej: PROBABILITY)
Extensiones Opcionales
Extensión 1: Caché de Respuestas
import hashlib
import json
import os
class ReasoningEngineConCache(ReasoningEngine):
"""Extensión del engine con caché en disco para desarrollo."""
def __init__(self, cache_path: str = ".reasoning_cache.json", **kwargs):
super().__init__(**kwargs)
self.cache_path = cache_path
self._cache = self._cargar_cache()
def _cargar_cache(self) -> dict:
if os.path.exists(self.cache_path):
with open(self.cache_path, 'r') as f:
return json.load(f)
return {}
def _guardar_cache(self):
with open(self.cache_path, 'w') as f:
json.dump(self._cache, f, indent=2, ensure_ascii=False)
def resolver(self, problema: str, **kwargs) -> 'ResultadoReasoning':
cache_key = hashlib.md5(f"{problema}{kwargs}".encode()).hexdigest()
if cache_key in self._cache:
print("[Desde caché]")
# Aquí reconstruirías el ResultadoReasoning desde el caché
# Simplificado para este ejemplo
resultado = super().resolver(problema, **kwargs)
# Guardar en caché
self._cache[cache_key] = {
"problema": problema,
"respuesta": resultado.respuesta,
"confianza": resultado.confianza,
"tokens": resultado.tokens_usados
}
self._guardar_cache()
return resultado
Extensión 2: Batch Processing
from concurrent.futures import ThreadPoolExecutor, as_completed
def resolver_batch(
problemas: list[str],
engine: ReasoningEngine,
max_workers: int = 3
) -> list[ResultadoReasoning]:
"""
Resuelve múltiples problemas en paralelo usando ThreadPoolExecutor.
Nota: La API de OpenAI tiene rate limits; max_workers debe ser conservador.
"""
resultados = [None] * len(problemas)
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futuras = {
executor.submit(engine.resolver, p): i
for i, p in enumerate(problemas)
}
for futura in as_completed(futuras):
idx = futuras[futura]
try:
resultados[idx] = futura.result()
print(f"✓ Problema {idx + 1} completado")
except Exception as e:
print(f"✗ Problema {idx + 1} falló: {e}")
return [r for r in resultados if r is not None]
Resumen del Proyecto
- ReasoningEngine integra Zero-Shot CoT, detección de tipo, y múltiples modos de verificación
- Verificación: none → sin costo; self → económico; backward → balanceado; two_pass → más robusto pero 2x costo
- Confidence scoring: Combina el score del CoT + resultado de verificación + calidad del parsing
- Benchmark: Mide mejora real de CoT en tu dataset específico
- CLI: Permite experimentación interactiva sin escribir código
- Extensibilidad: La arquitectura permite añadir fácilmente nuevos tipos de problemas y modos de verificación
Recursos adicionales
- Chain-of-Thought Prompting (Wei et al., 2022)
- Large Language Models are Zero-Shot Reasoners (Kojima et al., 2022)
- Self-Consistency Improves CoT (Wang et al., 2022)
- GSM8K Dataset - Grade School Math Problems
- OpenAI Python SDK - async support
- Pydantic v2 Documentation
- Python dataclasses - Official Docs