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 ReasoningEngine con 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

  1. Chain-of-Thought Prompting (Wei et al., 2022)
  2. Large Language Models are Zero-Shot Reasoners (Kojima et al., 2022)
  3. Self-Consistency Improves CoT (Wang et al., 2022)
  4. GSM8K Dataset - Grade School Math Problems
  5. OpenAI Python SDK - async support
  6. Pydantic v2 Documentation
  7. Python dataclasses - Official Docs