File size: 5,570 Bytes
9d29c62 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | import sympy as sp
class MathContext:
"""
A sandbox for the LLM to execute mathematics.
Each operation is recorded with Hebrew context and exact LaTeX representation.
"""
def __init__(self):
self.steps = []
# Injected SymPy helpers
self.sp = sp
self.Eq = sp.Eq
self.solve = sp.solve
self.expand = sp.expand
self.simplify = sp.simplify
self.sqrt = sp.sqrt
self.diff = sp.diff
def _format_latex(self, expr) -> str:
"""Converts SymPy to precise LaTeX, without $$ so it renders correctly in Flutter."""
if isinstance(expr, str):
# If the LLM passed a string instead of SymPy object, try parsing it
try:
expr = sp.sympify(expr)
except:
pass # Return as-is if not parseable
latex_str = sp.latex(expr)
return latex_str
def explain(self, text: str):
"""Adds a pure Hebrew explanation step without math."""
self.steps.append({
"content_mixed": text,
"block_math": ""
})
def declare_equation(self, text: str, eq: sp.Eq):
"""Prints a known equation with its explanation."""
self.steps.append({
"content_mixed": text,
"block_math": self._format_latex(eq)
})
return eq
def expand_expr(self, text: str, expr):
"""Expands an expression completely (e.g. squaring a root)."""
expanded = sp.expand(expr)
self.steps.append({
"content_mixed": text,
"block_math": self._format_latex(expanded)
})
return expanded
def solve_equation(self, text: str, eq: sp.Eq, var):
"""Solves an equation for a specific variable."""
solutions = sp.solve(eq, var)
# Format solutions nicely
if isinstance(solutions, list):
if len(solutions) == 1:
math_result = sp.Eq(var, solutions[0])
else:
# E.g. x_1 = 2, x_2 = -2
parts = [f"{sp.latex(var)}_{{{i+1}}} = {sp.latex(sol)}" for i, sol in enumerate(solutions)]
math_result = " \\text{ 讜讗讜 } ".join(parts)
else:
math_result = sp.Eq(var, solutions)
self.steps.append({
"content_mixed": text,
"block_math": math_result if isinstance(math_result, str) else self._format_latex(math_result)
})
return solutions
def finish(self, final_answer: str, teacher_summary: str = ""):
"""Sets the final human-readable answer for the UI."""
self.final_answer = final_answer
self.teacher_summary = teacher_summary
def run_llm_code(python_code: str) -> dict:
"""
Executes the LLM-generated Python code in our secure MathContext.
Returns the step-by-step UI format required by BuddyMath.
"""
ctx = MathContext()
# Secure Globals the LLM is allowed to use
safe_globals = {
"ctx": ctx,
"x": sp.Symbol('x'),
"y": sp.Symbol('y'),
"a": sp.Symbol('a'),
"b": sp.Symbol('b'),
"c": sp.Symbol('c'),
"m": sp.Symbol('m'),
"R": sp.Symbol('R'),
"sp": sp,
}
try:
# Execute the LLM's dynamically generated mathematics
exec(python_code, safe_globals)
return {
"success": True,
"steps": ctx.steps,
"final_answer": getattr(ctx, 'final_answer', "讛讙注谞讜 诇驻转专讜谉."),
"teacher_summary": getattr(ctx, 'teacher_summary', "")
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
if __name__ == "__main__":
# Simulate LLM output to solve a Locus / Parabola problem:
# "The distance from (x, y) to (2, 0) is equal to its distance to x = -2."
llm_code = """
ctx.explain("谞住诪谉 讗转 讛谞拽讜讚讛 讛讻诇诇讬转 注诇 讛诪拽讜诐 讛讙讬讗讜诪讟专讬 讻- (x,y). 诇驻讬 讛谞转讜谉, 讛诪专讞拽 诪讛诪讜拽讚 砖讜讜讛 诇诪专讞拽 诪讛诪讚专讬讱.")
d1 = sp.sqrt((x - 2)**2 + (y - 0)**2) # 诪讜拽讚
d2 = sp.sqrt((x - (-2))**2) # 诪讚专讬讱
eq1 = ctx.declare_equation("讛诪砖讜讜讗讛 讛诪砖讜讜讛 讘讬谉 讛诪专讞拽讬诐 讛讬讗:", ctx.Eq(d1, d2))
ctx.explain("谞注诇讛 讗转 砖谞讬 讛讗讙驻讬诐 讘专讬讘讜注 讻讚讬 诇讛讬驻讟专 诪讛砖讜专砖:")
# SymPy understands squaring both sides! We square them and declare equality.
squared_eq = ctx.Eq(d1**2, d2**2)
ctx.steps[-1]["block_math"] = ctx._format_latex(squared_eq) # Override previous block math
# Now expand and simplify it beautifully
expanded_eq = ctx.declare_equation("谞专讞讬讘 讗转 讛讘讬讟讜讬讬诐 (驻转讬讞转 住讜讙专讬讬诐 诪诇讗讛):", ctx.Eq(ctx.expand(squared_eq.lhs), ctx.expand(squared_eq.rhs)))
# SymPy's powerful simplify equation solver (subtract RHS from LHS)
simplified_expr = ctx.simplify(expanded_eq.lhs - expanded_eq.rhs)
final_eq = ctx.declare_equation("诇讗讞专 讻讬谞讜住 讗讬讘专讬诐 讜讛注讘专转 讗讙驻讬诐, 谞拽讘诇 讗转 爪讜专转 讛驻专讘讜诇讛 讛驻砖讜讟讛:", ctx.Eq(simplified_expr, 0))
# Also isolate y^2 if needed
y_sq_isolated = ctx.solve(final_eq, y**2)
if y_sq_isolated:
ctx.declare_equation("谞讘讜讚讚 讗转 y^2 讘诪砖讜讜讗讛:", ctx.Eq(y**2, y_sq_isolated[0]))
ctx.finish("$$ y^2 = 8x $$")
"""
print("馃殌 Running LLM Mathematics Script:")
result = run_llm_code(llm_code)
import json
# Print the resulting UI object
print(json.dumps(result, indent=2, ensure_ascii=False))
|