| """ |
| 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"], |
| } |
| |
| |
| 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, |
| }) |
| |
| |
| 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 |
|
|