Module 4: Chain-of-Thought and Reasoning
8. Project: Reasoning Engine with Verifiable CoT
Overview
Build a complete reasoning engine that solves math and logic problems using CoT, verifies each step, reports a confidence score, and can compare its performance with and without CoT. This project integrates everything you learned in the module: Zero-Shot CoT, Manual CoT, Verification Patterns, and the principles of when not to use CoT.
Estimated time: 120-180 minutes
What you'll build:
- A
ReasoningEngineclass with support for multiple domains - A multi-step verification system (backward check + constraint verification)
- Calibrated confidence scoring
- A CoT vs. no-CoT comparison benchmark
- A CLI for interactive use
Project Specifications
Inputs
- Free text: math or logic problems
- Optional problem type:
math,logic,general - Verification mode:
none,self,backward,two_pass
Outputs
{
"problema": "...",
"tipo": "math",
"razonamiento": "Step 1: ...\nStep 2: ...",
"respuesta": "391",
"verificacion": {
"metodo": "backward",
"resultado": "VERIFIED",
"nota": "391/17=23 ✓"
},
"confianza": {
"score": 0.95,
"nivel": "VERY_HIGH",
"factores": {
"certeza_proceso": 1.0,
"verificabilidad": 1.0,
"claridad_problema": 0.9,
"conocimiento_dominio": 0.9
}
},
"tokens_usados": 342,
"tiempo_ms": 1240
}
Project Structure
reasoning_engine/
├── engine.py # The main ReasoningEngine class
├── verifiers.py # Verification module
├── parsers.py # Extraction of answers from the CoT
├── prompts.py # Prompt templates per domain
├── benchmark.py # Benchmark system
├── cli.py # Command-line interface
└── problems.py # Test problem dataset
Implementation: prompts.py
# prompts.py
"""CoT prompt templates per domain."""
MATH_COT_PROMPT = """Solve the math problem step by step.
For each step:
1. Describe the operation
2. Show the calculation
3. Write the intermediate result
At the end, write EXACTLY:
ANSWER: [number or expression]
CONFIDENCE: [number between 0 and 1]
Problem: {problema}
"""
LOGIC_COT_PROMPT = """Analyze the logical argument step by step.
1. Identify the PREMISES (numbered list)
2. Identify the CONCLUSION to be evaluated
3. Determine the logical form (modus ponens, modus tollens, syllogism, etc.)
4. Check whether the conclusion follows from the premises
5. Look for counterexamples if you suspect it's invalid
At the end, write EXACTLY:
ANSWER: VALID / INVALID
REASONING: [one line explaining why]
CONFIDENCE: [number between 0 and 1]
Argument: {problema}
"""
GENERAL_COT_PROMPT = """Solve the problem step by step.
Analyze it carefully, show your reasoning, and reach a clear conclusion.
At the end, write EXACTLY:
ANSWER: [your answer]
CONFIDENCE: [number between 0 and 1, where 1 = total certainty]
Problem: {problema}
"""
DIRECT_PROMPT = """Answer directly and concisely.
Problem: {problema}
Answer:"""
VERIFICATION_BACKWARD_MATH = """Verify that this answer is correct by substituting backward.
Original problem: {problema}
Proposed answer: {respuesta}
Verification steps:
1. Take the proposed value
2. Apply it to the original problem
3. Check that every condition holds
Write:
STATUS: VERIFIED / ERROR
DETAILS: [what you verified and how]
CORRECTED_ANSWER: [only if there was an error, the correct answer]
"""
VERIFICATION_LOGIC = """Verify the conclusion of the logical argument.
Original problem: {problema}
Proposed conclusion: {respuesta}
To verify:
1. Does the conclusion follow from the premises by some valid rule?
2. Is there any counterexample where the premises are true and the conclusion is false?
Write:
STATUS: VERIFIED / ERROR
DETAILS: [the inference rule used, or the counterexample you found]
"""
Implementation: parsers.py
# parsers.py
"""Module for extracting answers from CoT outputs."""
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:
"""
Extracts the numeric answer and the confidence from a math CoT output.
Tries several strategies in order of reliability.
"""
razonamiento = output
# Strategy 1: Look for the explicit ANSWER: label
match_resp = re.search(
r'^ANSWER:\s*([\d,.$€%\.±\-\+\/]+)',
output,
re.MULTILINE | re.IGNORECASE
)
if match_resp:
respuesta = match_resp.group(1).strip().replace(',', '')
# Look for the confidence
match_conf = re.search(
r'^CONFIDENCE:\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="explicit_label"
)
# Strategy 2: Look for conclusion phrases
patrones_conclusion = [
r'(?:therefore|thus|in conclusion|the answer is)[,:\s]+\$?([\d,\.]+)',
r'(?:total|result|sum)[:\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="conclusion_pattern"
)
# Strategy 3: The last number in the text (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="last_number_fallback"
)
return ParsedOutput(
respuesta=None,
confianza=None,
razonamiento=output,
parse_exitoso=False,
metodo_extraccion="none"
)
def parse_respuesta_logica(output: str) -> ParsedOutput:
"""Extracts the VALID/INVALID verdict from a logic CoT output."""
# Look for the explicit label
match = re.search(
r'^ANSWER:\s*(VALID|INVALID)',
output,
re.MULTILINE | re.IGNORECASE
)
if match:
confianza_match = re.search(
r'^CONFIDENCE:\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="explicit_label"
)
# Search the final part of the text
ultima_parte = output[-400:].upper()
if "INVALID" in ultima_parte:
return ParsedOutput(
respuesta="INVALID",
confianza=None,
razonamiento=output,
parse_exitoso=True,
metodo_extraccion="text_search"
)
elif "VALID" in ultima_parte:
return ParsedOutput(
respuesta="VALID",
confianza=None,
razonamiento=output,
parse_exitoso=True,
metodo_extraccion="text_search"
)
return ParsedOutput(
respuesta=None,
confianza=None,
razonamiento=output,
parse_exitoso=False,
metodo_extraccion="none"
)
Implementation: verifiers.py
# verifiers.py
"""Verification system for CoT answers."""
from openai import OpenAI
from dataclasses import dataclass
client = OpenAI()
@dataclass
class VerificationResult:
metodo: str
estado: str # "VERIFIED", "ERROR", "UNDETERMINED"
detalles: str
respuesta_corregida: str | None = None
confianza_verificacion: float | None = None
def verificar_backward_matematico(
problema: str,
respuesta: str
) -> VerificationResult:
"""
Verifies a math answer by substituting backward.
"""
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'STATUS:\s*(VERIFIED|ERROR)', output, re.IGNORECASE)
correccion_match = re.search(r'CORRECTED_ANSWER:\s*(.+?)(?:\n|$)', output, re.IGNORECASE)
estado = estado_match.group(1).upper() if estado_match else "UNDETERMINED"
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 == "VERIFIED" else 0.3 if estado == "ERROR" else 0.5
)
def verificar_logica(
problema: str,
respuesta: str
) -> VerificationResult:
"""
Verifies a piece of logical reasoning by looking for counterexamples.
"""
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'STATUS:\s*(VERIFIED|ERROR)', output, re.IGNORECASE)
estado = estado_match.group(1).upper() if estado_match else "UNDETERMINED"
return VerificationResult(
metodo="verificacion_logica",
estado=estado,
detalles=output,
confianza_verificacion=1.0 if estado == "VERIFIED" else 0.3
)
def calcular_confidence_score(
parsed_output, # ParsedOutput
verification_result: VerificationResult,
tipo_problema: str
) -> dict:
"""
Computes the final confidence score, combining the CoT confidence + the verification.
"""
# The CoT's own base score (if the model provided one)
cot_confidence = parsed_output.confianza or 0.7 # Default 0.7 when there's no score
# Adjustment based on the verification
if verification_result.estado == "VERIFIED":
verificacion_factor = 1.0
elif verification_result.estado == "UNDETERMINED":
verificacion_factor = 0.8
else: # ERROR
verificacion_factor = 0.2
# Adjustment based on the extraction method
parse_factor = {
"explicit_label": 1.0,
"conclusion_pattern": 0.9,
"last_number_fallback": 0.7,
"none": 0.1
}.get(parsed_output.metodo_extraccion, 0.7)
# Final weighted score
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 to [0, 1]
nivel = (
"VERY_HIGH" if score_final >= 0.9 else
"HIGH" if score_final >= 0.7 else
"MEDIUM" if score_final >= 0.5 else
"LOW"
)
return {
"score": round(score_final, 3),
"nivel": nivel,
"factores": {
"cot_confidence": cot_confidence,
"verificacion_factor": verificacion_factor,
"parse_factor": parse_factor
}
}
Main Implementation: engine.py
# engine.py
"""The main reasoning engine with verifiable CoT."""
import time
import re
from dataclasses import dataclass, field
from enum import Enum
from openai import OpenAI
# Import the project modules
# (In a real file, import from the corresponding modules)
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:
"""
A reasoning engine with verifiable CoT.
Supports multiple problem types and verification modes,
and produces calibrated confidence scores.
Usage example:
engine = ReasoningEngine()
resultado = engine.resolver("What is 17 × 23?")
print(resultado.respuesta) # "391"
print(resultado.confianza["nivel"]) # "VERY_HIGH"
"""
PROMPTS = {
TipoProblema.MATH: """Solve the math problem step by step.
For each step:
1. Describe the operation
2. Show the calculation
3. Write the intermediate result
At the end, write EXACTLY:
ANSWER: [number or expression]
CONFIDENCE: [number between 0 and 1]
Problem: {problema}
""",
TipoProblema.LOGIC: """Analyze the logical argument step by step.
1. List the PREMISES
2. Identify the CONCLUSION
3. Determine whether the conclusion follows necessarily
4. Look for counterexamples if you suspect it's invalid
At the end, write EXACTLY:
ANSWER: VALID / INVALID
REASONING: [one line]
CONFIDENCE: [number between 0 and 1]
Argument: {problema}
""",
TipoProblema.GENERAL: """Solve it step by step.
At the end, write EXACTLY:
ANSWER: [your answer]
CONFIDENCE: [number between 0 and 1]
Problem: {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:
"""Automatically detects the type of problem."""
problema_lower = problema.lower()
keywords_math = [
"how much", "calculate", "solve", "how many", "sum", "subtract",
"multiply", "divide", "percent", "discount", "price",
"equation", "algebra", "integral", "derivative", "+", "-", "×"
]
keywords_logic = [
"if...then", "therefore", "implies", "valid", "invalid",
"premise", "conclusion", "all the", "some", "no ",
"logic", "argument", "does it follow"
]
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:
"""Builds the CoT prompt appropriate for the problem type."""
template = self.PROMPTS[tipo]
return template.format(problema=problema)
def _parsear_output(self, output: str, tipo: TipoProblema) -> tuple[str | None, float | None, bool]:
"""
Extracts the answer and the confidence from the CoT output.
Returns:
tuple of (answer, confidence, parse_succeeded)
"""
# Look for the ANSWER: label
match_resp = re.search(
r'^ANSWER:\s*(.+?)$',
output,
re.MULTILINE | re.IGNORECASE
)
# Look for the CONFIDENCE: label
match_conf = re.search(
r'^CONFIDENCE:\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: look for conclusion patterns
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 "INVALID" in ultima:
respuesta = "INVALID"
parse_exitoso = True
elif "VALID" in ultima:
respuesta = "VALID"
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:
"""Verifies the answer according to the selected mode."""
if modo == ModoVerificacion.NONE:
return None
if modo == ModoVerificacion.SELF:
prompt = f"""Verify whether this answer is correct.
Problem: {problema}
Proposed answer: {respuesta}
Is it correct? Respond VERIFIED or ERROR. Explain briefly."""
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 = "VERIFIED" if "verified" in output_v.lower() else "UNDETERMINED"
return VerificationInfo("self_verification", estado, output_v)
elif modo == ModoVerificacion.BACKWARD:
if tipo == TipoProblema.MATH:
prompt = f"""Verify by substituting the answer back into the original problem.
Problem: {problema}
Answer: {respuesta}
Substitute and verify. Write VERIFIED ✓ or ERROR ✗ + the correction if it applies."""
else:
prompt = f"""Verify this logical argument by looking for counterexamples.
Argument: {problema}
Conclusion: {respuesta}
Is there a counterexample? Write VERIFIED ✓ or 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 "verified" in output_v.lower():
estado = "VERIFIED"
elif "✗" in output_v or "error" in output_v.lower():
estado = "ERROR"
# Try to extract the correction
match_corr = re.search(r'(?:correction|correct)[:\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 = "UNDETERMINED"
return VerificationInfo("backward", estado, output_v)
elif modo == ModoVerificacion.TWO_PASS:
prompt = f"""You are an independent auditor. Solve the problem YOURSELF
and check whether the proposed solution is correct.
Problem: {problema}
Proposed solution: {respuesta}
Your independent solution:
[reason here]
Verdict: CORRECT or INCORRECT
If INCORRECT: CORRECT_SOLUTION: [answer]"""
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 "correct" in output_v.lower() and "incorrect" not in output_v.lower():
estado = "VERIFIED"
elif "incorrect" in output_v.lower():
estado = "ERROR"
match_sol = re.search(r'CORRECT_SOLUTION:\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 = "UNDETERMINED"
return VerificationInfo("two_pass", estado, output_v)
return None
def _calcular_confianza(
self,
confianza_cot: float | None,
verificacion: VerificationInfo | None,
parse_exitoso: bool
) -> dict:
"""Computes the final confidence score."""
base = confianza_cot or 0.65
verificacion_factor = 1.0
if verificacion:
if verificacion.estado == "VERIFIED":
verificacion_factor = 1.0
elif verificacion.estado == "UNDETERMINED":
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 = (
"VERY_HIGH" if score >= 0.9 else
"HIGH" if score >= 0.7 else
"MEDIUM" if score >= 0.5 else
"LOW"
)
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:
"""
Solves a problem with optional CoT and verification.
Args:
problema: The problem to solve
tipo: The problem type (auto-detected if None)
verificacion: The verification mode to use
usar_cot: If False, answers directly with no CoT
Returns:
ResultadoReasoning with every detail
"""
t_inicio = time.time()
# Detect the type if it wasn't specified
tipo_detectado = tipo or self.detectar_tipo(problema)
# Build the prompt
if usar_cot:
prompt = self._construir_prompt_cot(problema, tipo_detectado)
modo_cot = "chain_of_thought"
else:
prompt = f"Answer directly: {problema}"
modo_cot = "direct"
# Call the model
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
# Parse the answer
respuesta, confianza_cot, parse_ok = self._parsear_output(output, tipo_detectado)
# Verify if CoT is active and there is an answer
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
)
# Use the corrected answer if the verification found an error
if verificacion_result and verificacion_result.respuesta_corregida:
respuesta = verificacion_result.respuesta_corregida
# Compute the final confidence
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
)
Implementation: benchmark.py
# benchmark.py
"""Benchmark system for comparing 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 of problems with known answers
PROBLEMAS_MATH = [
# (problem, correct_answer)
("What is 17 × 23?", "391"),
("A product costs $80 with a 20% discount. What is the original price?", "100"),
("If A finishes in 6h and B in 4h, how long do they take together?", "2.4"),
("What is 15% of $240?", "36"),
("Train A leaves at 80 km/h. Train B leaves 30 min later at 120 km/h. When does B catch A?", "1"),
("How many integers from 1 to 50 are divisible by 3 or by 5?", "23"),
("I invest $1000 at 5% compounded annually for 2 years. How much do I have?", "1102.5"),
("The hypotenuse of a right triangle with legs 6 and 8 is:", "10"),
]
PROBLEMAS_LOGIC = [
# (problem, correct_answer)
("If P then Q. P is true. Is Q true?", "VALID"),
("If it rains, the ground gets wet. The ground is wet. Is it raining?", "INVALID"),
("All cats are animals. Misi is a cat. Is Misi an animal?", "VALID"),
("Some dogs bark. Rex is a dog. Does Rex bark?", "INVALID"),
("If I don't study, I fail. I don't fail. Did I study?", "VALID"),
]
def ejecutar_benchmark(
n_problemas_math: int = 5,
n_problemas_logic: int = 3,
modo_verificacion: ModoVerificacion = ModoVerificacion.BACKWARD
) -> ResultadoBenchmark:
"""
Runs the complete CoT vs. no-CoT benchmark.
Args:
n_problemas_math: Number of math problems to use
n_problemas_logic: Number of logic problems to use
modo_verificacion: The verification mode for the CoT engine
Returns:
ResultadoBenchmark with detailed statistics
"""
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. No CoT ({len(todos_problemas)} problems)")
print(f"{'='*70}")
for problema, respuesta_correcta, tipo in todos_problemas:
print(f"\n► {problema[:60]}...")
# With CoT
r_cot = engine.resolver(
problema,
tipo=tipo,
verificacion=modo_verificacion,
usar_cot=True
)
# Without CoT
r_sin = engine.resolver(
problema,
tipo=tipo,
verificacion=ModoVerificacion.NONE,
usar_cot=False
)
# Check whether they're correct
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" With CoT: {'✓' if cot_ok else '✗'} | Ans: {r_cot.respuesta} | Confidence: {r_cot.confianza['nivel']} | {r_cot.tokens_usados} tokens")
print(f" Without CoT: {'✓' if sin_ok else '✗'} | Ans: {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("BENCHMARK SUMMARY")
print(f"{'='*70}")
print(f"Accuracy with CoT: {acc_cot:.1%} ({correctos_cot}/{n})")
print(f"Accuracy without CoT: {acc_sin:.1%} ({correctos_sin_cot}/{n})")
print(f"CoT improvement: +{resultado.mejora_accuracy:.1%}")
print(f"Tokens with CoT: {resultado.tokens_promedio_cot:.0f} (average)")
print(f"Tokens without CoT: {resultado.tokens_promedio_sin_cot:.0f} (average)")
print(f"CoT multiplier: {resultado.multiplicador_tokens:.1f}x more tokens")
return resultado
Implementation: cli.py
# cli.py
"""Command-line interface for the Reasoning Engine."""
import sys
from engine import ReasoningEngine, TipoProblema, ModoVerificacion
def main():
"""Interactive CLI for the Reasoning Engine."""
engine = ReasoningEngine()
print("\n" + "="*60)
print("REASONING ENGINE - Verifiable CoT")
print("="*60)
print("Commands:")
print(" [problem] → Solve with the default configuration")
print(" :type math → Set the type (math/logic/general)")
print(" :verification backward → Mode (none/self/backward/two_pass)")
print(" :benchmark → Run the complete benchmark")
print(" :exit → Exit")
print("="*60)
tipo_actual = None
verificacion_actual = ModoVerificacion.BACKWARD
while True:
try:
entrada = input("\n> ").strip()
except (KeyboardInterrupt, EOFError):
print("\nSee you!")
sys.exit(0)
if not entrada:
continue
if entrada.lower() == ":exit":
print("See you!")
break
elif entrada.lower().startswith(":type "):
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"Type set to: {tipo_actual.value}")
else:
print(f"Type not recognized. Options: {list(mapa.keys())}")
elif entrada.lower().startswith(":verification "):
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"Verification: {verificacion_actual.value}")
else:
print(f"Mode not recognized. Options: {list(mapa_v.keys())}")
elif entrada.lower() == ":benchmark":
from benchmark import ejecutar_benchmark
ejecutar_benchmark(n_problemas_math=5, n_problemas_logic=3)
else:
# Solve the problem
print(f"\n[Solving with CoT + {verificacion_actual.value} verification...]")
resultado = engine.resolver(
entrada,
tipo=tipo_actual,
verificacion=verificacion_actual
)
print(f"\n── REASONING ──")
print(resultado.razonamiento)
print(f"\n── RESULT ──")
print(f"Detected type: {resultado.tipo.value}")
print(f"Answer: {resultado.respuesta}")
print(f"Confidence: {resultado.confianza['score']} ({resultado.confianza['nivel']})")
if resultado.verificacion:
estado_emoji = "✓" if resultado.verificacion.estado == "VERIFIED" else "✗" if resultado.verificacion.estado == "ERROR" else "?"
print(f"Verification: {estado_emoji} {resultado.verificacion.estado} ({resultado.verificacion.metodo})")
print(f"Tokens used: {resultado.tokens_usados}")
print(f"Time: {resultado.tiempo_ms}ms")
if __name__ == "__main__":
main()
Running It and Demo
# demo.py - Run this to see the engine in action
from engine import ReasoningEngine, TipoProblema, ModoVerificacion
from benchmark import ejecutar_benchmark
def demo_basico():
"""Demonstrates the basic usage of the Reasoning Engine."""
engine = ReasoningEngine()
print("=" * 60)
print("DEMO: Reasoning Engine with Verifiable CoT")
print("=" * 60)
# A math problem
print("\n1. MATH PROBLEM")
resultado = engine.resolver(
"If I invest $5,000 at 8% compounded annually for 3 years, how much do I have?",
tipo=TipoProblema.MATH,
verificacion=ModoVerificacion.BACKWARD
)
print(f"Answer: {resultado.respuesta}")
print(f"Confidence: {resultado.confianza['score']} ({resultado.confianza['nivel']})")
verificacion_estado = resultado.verificacion.estado if resultado.verificacion else "N/A"
print(f"Verification: {verificacion_estado}")
# Expected answer: $6,298.56 (approximately)
# A logic problem
print("\n2. LOGIC PROBLEM")
resultado_logico = engine.resolver(
"If you study, you pass the exam. You didn't pass the exam. Did you study?",
tipo=TipoProblema.LOGIC,
verificacion=ModoVerificacion.BACKWARD
)
print(f"Answer: {resultado_logico.respuesta}")
print(f"Confidence: {resultado_logico.confianza['score']}")
# Expected answer: VALID (modus tollens)
# CoT vs. no-CoT comparison
print("\n3. COMPARISON CoT vs. No CoT")
problema_test = "What is 23% of $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"With CoT: {r_cot.respuesta} ({r_cot.tokens_usados} tokens)")
print(f"Without CoT: {r_sin.respuesta} ({r_sin.tokens_usados} tokens)")
print(f"Expected answer: 103.5")
def demo_benchmark():
"""Runs the complete benchmark."""
resultados = ejecutar_benchmark(
n_problemas_math=5,
n_problemas_logic=3,
modo_verificacion=ModoVerificacion.BACKWARD
)
print(f"\nCONCLUSION:")
if resultados.mejora_accuracy > 0.1:
print(f"CoT improves accuracy significantly (+{resultados.mejora_accuracy:.0%})")
print(f"The extra cost ({resultados.multiplicador_tokens:.1f}x tokens) is justified for this kind of problem.")
elif resultados.mejora_accuracy > 0:
print(f"CoT improves accuracy modestly (+{resultados.mejora_accuracy:.0%})")
print(f"Consider whether the extra cost is worth it for your use case.")
else:
print(f"CoT did not improve accuracy in this benchmark.")
print(f"Check whether these problems are appropriate for CoT.")
if __name__ == "__main__":
print("Running the basic demo...")
demo_basico()
print("\n\nRunning the benchmark...")
demo_benchmark()
Project Success Criteria
Check off each criterion as you complete it:
- Basic solving: The engine correctly solves 8/10 simple math problems
- Problem types: It auto-detects the type (math/logic) with ≥80% accuracy
- Backward verification: The verification detects errors and corrects the answer when possible
- Confidence score: The score reflects the real difficulty (simple problems → high confidence)
- Benchmark: It shows a measurable improvement of CoT vs. no CoT on the dataset's problems
- Working CLI: The CLI accepts free-text problems and shows formatted results
- Error handling: The engine doesn't crash if the API doesn't respond or if it can't parse the answer
- Extensibility: It's easy to add a new problem type (e.g. PROBABILITY)
Optional Extensions
Extension 1: Answer Cache
import hashlib
import json
import os
class ReasoningEngineConCache(ReasoningEngine):
"""An extension of the engine with an on-disk cache for development."""
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("[From cache]")
# Here you'd rebuild the ResultadoReasoning from the cache
# Simplified for this example
resultado = super().resolver(problema, **kwargs)
# Store in the cache
self._cache[cache_key] = {
"problema": problema,
"respuesta": resultado.respuesta,
"confianza": resultado.confianza,
"tokens": resultado.tokens_usados
}
self._guardar_cache()
return resultado
Extension 2: Batch Processing
from concurrent.futures import ThreadPoolExecutor, as_completed
def resolver_batch(
problemas: list[str],
engine: ReasoningEngine,
max_workers: int = 3
) -> list[ResultadoReasoning]:
"""
Solves multiple problems in parallel using ThreadPoolExecutor.
Note: The OpenAI API has rate limits; max_workers should stay conservative.
"""
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"✓ Problem {idx + 1} complete")
except Exception as e:
print(f"✗ Problem {idx + 1} failed: {e}")
return [r for r in resultados if r is not None]
Project Summary
- ReasoningEngine integrates Zero-Shot CoT, type detection, and multiple verification modes
- Verification: none → free; self → cheap; backward → balanced; two_pass → more robust but 2x the cost
- Confidence scoring: Combines the CoT's own score + the verification result + the quality of the parsing
- Benchmark: Measures the real improvement CoT gives you on your specific dataset
- CLI: Enables interactive experimentation without writing code
- Extensibility: The architecture makes it easy to add new problem types and verification modes
Additional resources
- 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