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))