pmary's picture
Upload small_reasoning_agent/math_solver.py with huggingface_hub
06403ac verified
Raw
History Blame Contribute Delete
9.96 kB
"""
End-to-End Math Solver with Confidence-Triggered Hints
You give it a math question. It:
1. Solves it step by step
2. Monitors confidence at each step
3. Automatically calls the HintTool when stuck
4. Returns the final answer
"""
import numpy as np
from typing import Optional, Dict, Any, List
from dataclasses import dataclass
from hint_tool import HintTool
@dataclass
class SolverResult:
"""Result from solving a math problem."""
question: str
final_answer: str
steps: List[Dict[str, Any]]
hints_used: List[Dict[str, Any]]
confidence_trace: List[float]
success: bool
class MathSolverWithHints:
"""
A math solver that uses hints when confidence is low.
"""
def __init__(
self,
hint_tool: Optional[HintTool] = None,
confidence_threshold: float = 0.5,
max_steps: int = 8,
):
self.hint_tool = hint_tool or HintTool()
self.confidence_threshold = confidence_threshold
self.max_steps = max_steps
def solve(self, question: str, problem_id: Optional[str] = None) -> SolverResult:
"""Solve a math problem with automatic hint triggering."""
problem_id = problem_id or self._detect_problem_type(question)
steps = []
hints_used = []
confidence_trace = []
current_answer = ""
for step_num in range(1, self.max_steps + 1):
step_result = self._reasoning_step(
question=question,
step_num=step_num,
previous_steps=steps,
hints_used=hints_used,
)
confidence = step_result["confidence"]
confidence_trace.append(confidence)
step_data = {
"step_num": step_num,
"thought": step_result["thought"],
"confidence": confidence,
"action": step_result["action"],
}
# Check if hint is needed
if confidence < self.confidence_threshold:
hint = self.hint_tool.forward(problem_id, confidence)
step_data["hint_triggered"] = True
step_data["hint"] = hint
hints_used.append({
"step": step_num,
"confidence": confidence,
"hint": hint,
})
# Re-reason with hint
step_result = self._reasoning_step(
question=question,
step_num=step_num,
previous_steps=steps,
hints_used=hints_used,
current_hint=hint,
)
step_data["thought_after_hint"] = step_result["thought"]
step_data["confidence_after_hint"] = step_result["confidence"]
else:
step_data["hint_triggered"] = False
steps.append(step_data)
if step_result.get("is_final_answer", False):
current_answer = step_result["answer"]
break
success = self._evaluate_answer(question, current_answer)
return SolverResult(
question=question,
final_answer=current_answer,
steps=steps,
hints_used=hints_used,
confidence_trace=confidence_trace,
success=success,
)
def _detect_problem_type(self, question: str) -> str:
"""Auto-detect problem category from question text."""
q = question.lower()
if any(word in q for word in ["solve for", "equation", "x +", "2x", "3x"]):
return "algebra_linear"
elif any(word in q for word in ["circle", "radius", "diameter", "circumference", "area of circle"]):
return "geometry_circle"
elif any(word in q for word in ["derivative", "d/dx", "slope", "rate of change"]):
return "calculus_derivative"
elif any(word in q for word in ["probability", "bayes", "chance", "odds"]):
return "probability_bayes"
elif any(word in q for word in ["prime", "divisible", "factor", "gcd", "lcm"]):
return "number_theory_prime"
elif any(word in q for word in ["fraction", "numerator", "denominator"]):
return "fractions_addition"
return "algebra_linear"
def _reasoning_step(self, question, step_num, previous_steps, hints_used, current_hint=None):
"""Perform one reasoning step (rule-based solver for demo)."""
q = question.lower()
if "solve for x" in q and "2x + 5 = 13" in q:
return self._solve_linear_2x_plus_5(step_num, current_hint)
elif "solve for x" in q and "3x - 7 = 14" in q:
return self._solve_linear_3x_minus_7(step_num, current_hint)
elif "area of circle" in q and "radius 5" in q:
return self._solve_circle_area(step_num, current_hint)
elif "derivative of x^2" in q:
return self._solve_derivative_x2(step_num, current_hint)
elif "prime" in q and "17" in q:
return self._solve_prime_17(step_num, current_hint)
else:
return self._generic_step(step_num, current_hint)
def _solve_linear_2x_plus_5(self, step_num, hint):
if step_num == 1:
conf = 0.8 if hint else 0.6
return {"thought": "Subtract 5 from both sides: 2x = 8", "confidence": conf, "action": "subtract 5", "is_final_answer": False}
elif step_num == 2:
conf = 0.9 if hint else 0.7
return {"thought": "Divide by 2: x = 4", "confidence": conf, "action": "divide by 2", "is_final_answer": True, "answer": "x = 4"}
return {"thought": "Done", "confidence": 0.9, "action": "none", "is_final_answer": True, "answer": "x = 4"}
def _solve_linear_3x_minus_7(self, step_num, hint):
if step_num == 1:
conf = 0.7 if hint else 0.4
return {"thought": "Add 7 to both sides: 3x = 21", "confidence": conf, "action": "add 7", "is_final_answer": False}
elif step_num == 2:
conf = 0.9 if hint else 0.6
return {"thought": "Divide by 3: x = 7", "confidence": conf, "action": "divide by 3", "is_final_answer": True, "answer": "x = 7"}
return {"thought": "Done", "confidence": 0.9, "action": "none", "is_final_answer": True, "answer": "x = 7"}
def _solve_circle_area(self, step_num, hint):
if step_num == 1:
conf = 0.6 if hint else 0.3
return {"thought": "Area = pi*r^2 = pi*25 = 25*pi ≈ 78.54", "confidence": conf, "action": "apply formula", "is_final_answer": True, "answer": "78.54 (or 25*pi)"}
return {"thought": "Done", "confidence": 0.9, "action": "none", "is_final_answer": True, "answer": "78.54"}
def _solve_derivative_x2(self, step_num, hint):
if step_num == 1:
conf = 0.85 if hint else 0.65
return {"thought": "Using power rule: d/dx(x^2) = 2x", "confidence": conf, "action": "power rule", "is_final_answer": True, "answer": "2x"}
return {"thought": "Done", "confidence": 0.9, "action": "none", "is_final_answer": True, "answer": "2x"}
def _solve_prime_17(self, step_num, hint):
if step_num == 1:
conf = 0.7 if hint else 0.45
return {"thought": "17 is only divisible by 1 and 17. No divisors between 2 and sqrt(17)≈4.1. So 17 is prime.", "confidence": conf, "action": "check divisibility", "is_final_answer": True, "answer": "Yes, 17 is prime"}
return {"thought": "Done", "confidence": 0.9, "action": "none", "is_final_answer": True, "answer": "Yes"}
def _generic_step(self, step_num, hint):
conf = 0.7 if hint else 0.4
return {"thought": f"Step {step_num}: Analyzing...", "confidence": conf, "action": "analyze", "is_final_answer": step_num >= 3, "answer": "Unable to solve"}
def _evaluate_answer(self, question, answer):
if not answer:
return False
correct = {
"2x + 5 = 13": "x = 4",
"3x - 7 = 14": "x = 7",
"radius 5": "78.54",
"x^2": "2x",
"17": "prime",
}
for key, val in correct.items():
if key in question.lower() and val in answer.lower():
return True
return False
def print_result(self, result):
"""Pretty print the solver result."""
print("=" * 60)
print("MATH SOLVER RESULT")
print("=" * 60)
print(f"\nQuestion: {result.question}")
print(f"Answer: {result.final_answer}")
print(f"Success: {result.success}")
print(f"\n--- Reasoning Steps ({len(result.steps)}) ---")
for step in result.steps:
print(f"\nStep {step['step_num']}:")
print(f" Thought: {step['thought']}")
print(f" Confidence: {step['confidence']:.3f}")
if step.get("hint_triggered"):
print(f" HINT USED: {step['hint'][:80]}...")
if "thought_after_hint" in step:
print(f" Revised thought: {step['thought_after_hint']}")
if result.hints_used:
print(f"\n--- Hints Used ({len(result.hints_used)}) ---")
for h in result.hints_used:
print(f" Step {h['step']}: {h['hint'][:60]}...")
else:
print("\n--- No hints needed ---")
print(f"\nConfidence trace: {[f'{c:.2f}' for c in result.confidence_trace]}")
print("=" * 60)
def solve_math(question: str, problem_id: Optional[str] = None, **kwargs) -> SolverResult:
"""Quick function to solve a math problem with hints."""
solver = MathSolverWithHints(**kwargs)
result = solver.solve(question, problem_id)
solver.print_result(result)
return result