developeranveshraman commited on
Commit
5d8fd4f
Β·
verified Β·
1 Parent(s): 19571d3

Upload 13 files

Browse files
advanced_math_engine.py ADDED
@@ -0,0 +1,994 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced Math Engine v2 β€” symbolic computation using SymPy.
3
+
4
+ Handles a wide range of advanced mathematics:
5
+ ─ Indefinite & definite integration
6
+ ─ Differentiation (any order, any variable)
7
+ ─ Limits (including one-sided and infinity)
8
+ ─ Equation & system solving
9
+ ─ Ordinary differential equations (ODEs)
10
+ ─ Matrix operations (det, inverse, eigenvalues, rank, trace)
11
+ ─ Taylor / Maclaurin series expansion
12
+ ─ Laplace & inverse Laplace transforms
13
+ ─ Fourier transform
14
+ ─ Simplification, factoring, expansion, partial fractions
15
+ ─ Number theory (GCD, LCM, prime factorization, modular arithmetic)
16
+ ─ Statistics (mean, variance, std deviation, median)
17
+ ─ Combinatorics (factorial, binomial coefficients, permutations)
18
+ ─ Complex number operations
19
+ ─ Summations & products
20
+ ─ Trigonometric identity simplification
21
+
22
+ The engine parses natural language ("integrate x^2 sin(x)"), runs the
23
+ computation symbolically with SymPy, and returns:
24
+ - a clean string result
25
+ - a LaTeX representation
26
+
27
+ The result is then handed to the LLM, which is TOLD the correct answer
28
+ and must only produce the step-by-step explanation β€” preventing hallucination.
29
+ """
30
+
31
+ import re
32
+ from typing import Optional, Tuple
33
+
34
+
35
+ # ─────────────────────────────────────────────────────────────────────────────
36
+ # Operation keyword registry
37
+ # ─────────────────────────────────────────────────────────────────────────────
38
+
39
+ _ADVANCED_OPS: dict[str, list[str]] = {
40
+ "integrate": [
41
+ "integrate", "integral of", "antiderivative of", "indefinite integral",
42
+ "definite integral", "∫",
43
+ ],
44
+ "differentiate": [
45
+ "differentiate", "derivative of", "d/dx", "d/dy", "d/dz", "d/dt",
46
+ "diff of", "first derivative", "second derivative", "third derivative",
47
+ "nth derivative", "partial derivative",
48
+ ],
49
+ "limit": [
50
+ "limit of", "limit as", "lim ", "lim(", "find the limit",
51
+ ],
52
+ "solve": [
53
+ "solve ", "find roots of", "zeros of", "find x such that",
54
+ "find the value of x", "find the solution",
55
+ ],
56
+ "ode": [
57
+ "differential equation", "ode ", "ordinary differential",
58
+ "dsolve", "solve the ode", "solve ode", "y'' ", "y' ",
59
+ "d2y", "d^2y", "solve the differential",
60
+ ],
61
+ "eigenvalue": [
62
+ "eigenvalue", "eigenvector", "eigen value", "eigen vector",
63
+ "characteristic polynomial",
64
+ ],
65
+ "determinant": [
66
+ "determinant of", "det of", "det(",
67
+ ],
68
+ "inverse": [
69
+ "inverse of matrix", "matrix inverse", "inverse matrix",
70
+ ],
71
+ "matrix_rank": [
72
+ "rank of matrix", "matrix rank", "rank(",
73
+ ],
74
+ "matrix_trace": [
75
+ "trace of matrix", "matrix trace", "trace(",
76
+ ],
77
+ "series": [
78
+ "taylor series", "maclaurin series", "series expansion",
79
+ "expand in series", "power series",
80
+ ],
81
+ "laplace": [
82
+ "laplace transform", "laplace of", "l{", "l(",
83
+ ],
84
+ "inverse_laplace": [
85
+ "inverse laplace", "laplace inverse", "l^-1",
86
+ ],
87
+ "fourier": [
88
+ "fourier transform", "fourier of",
89
+ ],
90
+ "simplify": [
91
+ "simplify ", "simplify(", "reduce ",
92
+ ],
93
+ "trig_simplify": [
94
+ "simplify trig", "trig simplif", "trigonometric simplif",
95
+ "simplify the trigonometric",
96
+ ],
97
+ "factor": [
98
+ "factor ", "factorise ", "factorize ", "factorise(", "factor(",
99
+ ],
100
+ "expand": [
101
+ "expand ", "expand(",
102
+ ],
103
+ "partial_fraction": [
104
+ "partial fraction", "partial fractions", "partial fraction decomposition",
105
+ ],
106
+ "gcd": [
107
+ "gcd(", "gcd of", "greatest common divisor", "highest common factor",
108
+ "hcf of",
109
+ ],
110
+ "lcm": [
111
+ "lcm(", "lcm of", "least common multiple", "lowest common multiple",
112
+ ],
113
+ "prime_factors": [
114
+ "prime factor", "prime factorization", "factorise into primes",
115
+ "factorize into primes", "prime decomposition",
116
+ ],
117
+ "modular": [
118
+ " mod ", "modulo ", "modular arithmetic", "modular inverse",
119
+ "congruence",
120
+ ],
121
+ "statistics": [
122
+ "mean of", "average of", "median of", "mode of",
123
+ "variance of", "standard deviation of", "std dev of", "std(",
124
+ "statistics of",
125
+ ],
126
+ "factorial": [
127
+ "factorial of", "factorial(", "! ", "n factorial",
128
+ ],
129
+ "binomial": [
130
+ "binomial coefficient", "choose ", "c(", "combinations of",
131
+ "nCr",
132
+ ],
133
+ "permutation": [
134
+ "permutation", "nPr", "arrangements of",
135
+ ],
136
+ "summation": [
137
+ "sum of ", "summation of", "sigma notation", "βˆ‘",
138
+ ],
139
+ "product": [
140
+ "product of ", "∏", "pi product",
141
+ ],
142
+ "complex_ops": [
143
+ "complex number", "real part", "imaginary part", "modulus of",
144
+ "argument of", "conjugate of",
145
+ ],
146
+ }
147
+
148
+
149
+ def detect_advanced_operation(text: str) -> Optional[str]:
150
+ """Return the detected advanced math operation (highest-priority match), or None."""
151
+ lowered = text.lower()
152
+
153
+ # Priority ordering β€” more specific ops first
154
+ priority_order = [
155
+ "trig_simplify", "inverse_laplace", "laplace", "fourier",
156
+ "ode", "eigenvalue", "determinant", "inverse", "matrix_rank",
157
+ "matrix_trace", "partial_fraction", "prime_factors", "modular",
158
+ "statistics", "binomial", "permutation", "factorial",
159
+ "summation", "product", "complex_ops", "gcd", "lcm",
160
+ "integrate", "differentiate", "limit", "series",
161
+ "simplify", "factor", "expand", "solve",
162
+ ]
163
+
164
+ for op in priority_order:
165
+ keywords = _ADVANCED_OPS.get(op, [])
166
+ for kw in keywords:
167
+ if kw in lowered:
168
+ return op
169
+ return None
170
+
171
+
172
+ # ─────────────────────────────────────────────────────────────────────────────
173
+ # Expression helpers
174
+ # ─────────────────────────────────────────────────────────────────────────────
175
+
176
+ def _preprocess(expr: str) -> str:
177
+ """Normalise user-written math to SymPy-parseable syntax."""
178
+ expr = expr.strip()
179
+ # Remove trailing differential (dx, dy, dt, …) for integrals
180
+ expr = re.sub(r'\s*d[a-zA-Z]\s*$', '', expr)
181
+ # Remove "= 0" for equation solving β€” SymPy's solve() takes LHS
182
+ expr = re.sub(r'\s*=\s*0\s*$', '', expr)
183
+ # Replace ^ with **
184
+ expr = expr.replace('^', '**')
185
+ # Natural log β†’ log
186
+ expr = re.sub(r'\bln\b', 'log', expr)
187
+ # arc functions
188
+ expr = re.sub(r'\barc(sin|cos|tan)\b', r'a\1', expr)
189
+ return expr.strip()
190
+
191
+
192
+ def _parse(expr_str: str):
193
+ """
194
+ Parse a string into a SymPy expression.
195
+ Uses implicit multiplication so "x sin(x)" β†’ x*sin(x).
196
+ Raises ValueError on failure.
197
+ """
198
+ from sympy.parsing.sympy_parser import (
199
+ parse_expr,
200
+ standard_transformations,
201
+ implicit_multiplication_application,
202
+ convert_xor,
203
+ )
204
+ from sympy import symbols
205
+ from sympy import (
206
+ sin, cos, tan, asin, acos, atan, sinh, cosh, tanh,
207
+ exp, log, sqrt, pi, E, oo, I, Abs,
208
+ sec, csc, cot, atan2, factorial, binomial,
209
+ ceiling, floor, sign, Heaviside,
210
+ )
211
+
212
+ transformations = standard_transformations + (
213
+ implicit_multiplication_application,
214
+ convert_xor,
215
+ )
216
+
217
+ local_dict = {v: symbols(v) for v in "xyztnkabcmnpqrs"}
218
+ local_dict.update({
219
+ "sin": sin, "cos": cos, "tan": tan,
220
+ "asin": asin, "acos": acos, "atan": atan,
221
+ "arcsin": asin, "arccos": acos, "arctan": atan,
222
+ "sinh": sinh, "cosh": cosh, "tanh": tanh,
223
+ "exp": exp, "log": log, "ln": log,
224
+ "sqrt": sqrt, "pi": pi, "e": E, "E": E,
225
+ "oo": oo, "inf": oo, "infinity": oo,
226
+ "I": I, "j": I, "abs": Abs, "Abs": Abs,
227
+ "sec": sec, "csc": csc, "cot": cot, "atan2": atan2,
228
+ "factorial": factorial, "binomial": binomial,
229
+ "ceil": ceiling, "floor": floor, "sign": sign,
230
+ "Heaviside": Heaviside, "H": Heaviside,
231
+ })
232
+
233
+ cleaned = _preprocess(expr_str)
234
+ try:
235
+ return parse_expr(cleaned, local_dict=local_dict,
236
+ transformations=transformations,
237
+ evaluate=True)
238
+ except Exception as exc:
239
+ raise ValueError(f"Cannot parse '{expr_str}': {exc}")
240
+
241
+
242
+ def _extract_variable(text: str, default: str = "x") -> str:
243
+ """Detect the primary variable from phrases like 'with respect to y'."""
244
+ m = re.search(r'with\s+respect\s+to\s+([a-zA-Z])', text, re.I)
245
+ if m:
246
+ return m.group(1)
247
+ m = re.search(r'\bwrt\s+([a-zA-Z])', text, re.I)
248
+ if m:
249
+ return m.group(1)
250
+ m = re.search(r'\bd/d([a-zA-Z])', text, re.I)
251
+ if m:
252
+ return m.group(1)
253
+ return default
254
+
255
+
256
+ def _strip_prefix(text: str, keywords: list[str]) -> str:
257
+ """Remove any matching operation prefix from the text."""
258
+ lowered = text.lower()
259
+ for kw in sorted(keywords, key=len, reverse=True):
260
+ if lowered.startswith(kw):
261
+ return text[len(kw):].strip()
262
+ for kw in sorted(keywords, key=len, reverse=True):
263
+ idx = lowered.find(kw)
264
+ if idx != -1:
265
+ return text[idx + len(kw):].strip()
266
+ return text.strip()
267
+
268
+
269
+ def _parse_matrix(text: str):
270
+ """Extract and parse a matrix from text like [[1,2],[3,4]]."""
271
+ from sympy import Matrix
272
+ m = re.search(r'\[\[.*?\]\]', text, re.DOTALL)
273
+ if not m:
274
+ raise ValueError(
275
+ "Please provide the matrix in format [[a,b],[c,d]] β€” e.g. [[1,2],[3,4]]"
276
+ )
277
+ mat_raw = m.group(0)
278
+ mat_data = eval(mat_raw)
279
+ return Matrix(mat_data)
280
+
281
+
282
+ # ─────────────────────────────────────────────────────────────────────────────
283
+ # Operation handlers
284
+ # ─────────────────────────────────────────────────────────────────────────────
285
+
286
+ def _handle_integrate(text: str) -> Tuple[str, str]:
287
+ from sympy import integrate, symbols, latex
288
+
289
+ var_name = _extract_variable(text)
290
+ var = symbols(var_name)
291
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["integrate"])
292
+ # Remove "with respect to X" from expression text
293
+ expr_text = re.sub(r'\s+with\s+respect\s+to\s+[a-zA-Z]\s*$', '', expr_text, flags=re.I).strip()
294
+ expr_text = re.sub(r'\bwrt\s+[a-zA-Z]\s*$', '', expr_text, flags=re.I).strip()
295
+
296
+ # Definite integral: "EXPR from A to B"
297
+ m = re.search(
298
+ r'(.*?)\s+from\s+([\w\.\-\+eEpioo∞infty]+)\s+to\s+([\w\.\-\+eEpioo∞infty]+)',
299
+ expr_text, re.I
300
+ )
301
+
302
+ def _parse_bound(raw: str):
303
+ raw = raw.replace("infty", "oo").replace("∞", "oo").replace("infinity", "oo")
304
+ import sympy
305
+ if raw == "oo": return sympy.oo
306
+ if raw == "-oo": return -sympy.oo
307
+ return _parse(raw)
308
+
309
+ if m:
310
+ expr = _parse(m.group(1).strip())
311
+ lower = _parse_bound(m.group(2).strip())
312
+ upper = _parse_bound(m.group(3).strip())
313
+ result = integrate(expr, (var, lower, upper))
314
+ return (
315
+ f"∫ ({expr}) d{var_name} from {lower} to {upper} = {result}",
316
+ latex(result),
317
+ )
318
+ else:
319
+ expr = _parse(expr_text)
320
+ result = integrate(expr, var)
321
+ return (
322
+ f"∫ ({expr}) d{var_name} = {result} + C",
323
+ latex(result) + " + C",
324
+ )
325
+
326
+
327
+ def _handle_differentiate(text: str) -> Tuple[str, str]:
328
+ from sympy import diff, symbols, latex
329
+
330
+ var_name = _extract_variable(text)
331
+ var = symbols(var_name)
332
+
333
+ _ORDINAL_MAP = {
334
+ "second": 2, "2nd": 2, "third": 3, "3rd": 3,
335
+ "fourth": 4, "4th": 4, "fifth": 5, "5th": 5,
336
+ "sixth": 6, "6th": 6, "seventh": 7, "7th": 7,
337
+ "eighth": 8, "8th": 8, "ninth": 9, "9th": 9,
338
+ }
339
+ order = 1
340
+ m_order = re.search(
341
+ r'\b(second|2nd|third|3rd|fourth|4th|fifth|5th|sixth|6th|'
342
+ r'seventh|7th|eighth|8th|ninth|9th)\s+derivative\b',
343
+ text, re.I
344
+ )
345
+ if m_order:
346
+ order = _ORDINAL_MAP[m_order.group(1).lower()]
347
+
348
+ expr_text = text
349
+ expr_text = re.sub(
350
+ r'(?:second|2nd|third|3rd|fourth|4th|fifth|5th|sixth|6th|'
351
+ r'seventh|7th|eighth|8th|ninth|9th)?\s*(?:partial\s+)?derivative\s+of\s+',
352
+ '', expr_text, flags=re.I
353
+ ).strip()
354
+ expr_text = _strip_prefix(expr_text, _ADVANCED_OPS["differentiate"])
355
+ expr_text = re.sub(r'^of\s+', '', expr_text, flags=re.I).strip()
356
+ expr_text = re.sub(r'\s+with\s+respect\s+to\s+[a-zA-Z]\s*$', '', expr_text, flags=re.I).strip()
357
+ expr_text = re.sub(r'\bwrt\s+[a-zA-Z]\s*$', '', expr_text, flags=re.I).strip()
358
+
359
+ expr = _parse(expr_text)
360
+ result = diff(expr, var, order)
361
+ order_label = {1: "d/d", 2: "dΒ²/d", 3: "dΒ³/d"}.get(order, f"d^{order}/d")
362
+ return (
363
+ f"{order_label}{var_name}[{expr}] = {result}",
364
+ latex(result),
365
+ )
366
+
367
+
368
+ def _handle_limit(text: str) -> Tuple[str, str]:
369
+ from sympy import limit, symbols, latex, oo
370
+
371
+ var_name = _extract_variable(text, default="x")
372
+ var = symbols(var_name)
373
+
374
+ m = re.search(
375
+ r'(?:limit\s+of\s+|lim\s+)?(.+?)\s+as\s+'
376
+ rf'{var_name}\s+(?:->|β†’|approaches|tends\s+to)\s+([^\s,]+)',
377
+ text, re.I
378
+ )
379
+
380
+ if m:
381
+ expr_raw = m.group(1).strip()
382
+ point_raw = m.group(2).strip()
383
+ else:
384
+ m2 = re.search(
385
+ rf'lim\s+{var_name}\s*[-β†’>]{{1,2}}\s*([^\s]+)\s+(.+)', text, re.I
386
+ )
387
+ if m2:
388
+ point_raw = m2.group(1)
389
+ expr_raw = m2.group(2)
390
+ else:
391
+ raise ValueError(
392
+ "Could not parse limit. Expected: 'limit of EXPR as x approaches VALUE'"
393
+ )
394
+
395
+ point_raw = (point_raw.replace("infinity", "oo")
396
+ .replace("∞", "oo")
397
+ .replace("infty", "oo"))
398
+ import sympy
399
+ if point_raw == "oo": point = oo
400
+ elif point_raw == "-oo": point = -oo
401
+ else: point = _parse(point_raw)
402
+
403
+ expr = _parse(expr_raw)
404
+ result = limit(expr, var, point)
405
+ return (
406
+ f"lim({expr}) as {var_name} β†’ {point} = {result}",
407
+ sympy.latex(result),
408
+ )
409
+
410
+
411
+ def _handle_solve(text: str) -> Tuple[str, str]:
412
+ from sympy import solve, symbols, Eq, latex
413
+
414
+ var_name = _extract_variable(text)
415
+ var = symbols(var_name)
416
+
417
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["solve"])
418
+ expr_text = re.sub(r'\s+for\s+[a-zA-Z]$', '', expr_text.strip(), flags=re.I)
419
+
420
+ if '=' in expr_text:
421
+ parts = expr_text.split('=', 1)
422
+ lhs = _parse(parts[0].strip())
423
+ rhs = _parse(parts[1].strip())
424
+ solutions = solve(Eq(lhs, rhs), var)
425
+ else:
426
+ solutions = solve(_parse(expr_text), var)
427
+
428
+ if not solutions:
429
+ return (f"No solutions found for: {expr_text}", r"\text{No solution}")
430
+
431
+ sol_str = ", ".join(str(s) for s in solutions)
432
+ sol_latex = ", ".join(latex(s) for s in solutions)
433
+ return (f"{var_name} = {sol_str}", sol_latex)
434
+
435
+
436
+ def _handle_ode(text: str) -> Tuple[str, str]:
437
+ """Solve ordinary differential equations using SymPy's dsolve."""
438
+ from sympy import symbols, Function, dsolve, latex, Eq, Derivative
439
+ from sympy.parsing.sympy_parser import parse_expr
440
+
441
+ x = symbols('x')
442
+ y = Function('y')
443
+
444
+ # Normalise ^ to **
445
+ text_norm = text.replace('^', '**')
446
+
447
+ # Try to extract the ODE expression:
448
+ # Support patterns like:
449
+ # "y'' + y = 0", "y' - 2y = 0", "dy/dx + y = x"
450
+ # We'll try to build the ODE equation
451
+
452
+ # Replace y'' β†’ Derivative(y(x), x, 2), y' β†’ Derivative(y(x), x)
453
+ # and y β†’ y(x) in the expression
454
+ cleaned = text_norm
455
+ # Strip any leading prompt words
456
+ cleaned = re.sub(
457
+ r'(?:solve|ode|ordinary differential equation|differential equation|solve the ode|solve ode)[\s:]*',
458
+ '', cleaned, flags=re.I
459
+ ).strip()
460
+
461
+ # Replace notation
462
+ cleaned = re.sub(r"y''", "Derivative(y(x),x,2)", cleaned)
463
+ cleaned = re.sub(r"y'", "Derivative(y(x),x)", cleaned)
464
+ # dy/dx or d^2y/dx^2
465
+ cleaned = re.sub(r'd\*\*2y/dx\*\*2', 'Derivative(y(x),x,2)', cleaned)
466
+ cleaned = re.sub(r'd2y/dx2', 'Derivative(y(x),x,2)', cleaned)
467
+ cleaned = re.sub(r'dy/dx', 'Derivative(y(x),x)', cleaned)
468
+ # bare y that isn't followed by ( β€” replace with y(x)
469
+ cleaned = re.sub(r'\by\b(?!\()', 'y(x)', cleaned)
470
+
471
+ local_dict = {
472
+ 'x': x, 'y': y, 'Derivative': Derivative,
473
+ }
474
+ from sympy import sin, cos, exp, log, sqrt, pi, E, oo, tan
475
+ local_dict.update({
476
+ 'sin': sin, 'cos': cos, 'exp': exp, 'log': log,
477
+ 'sqrt': sqrt, 'pi': pi, 'e': E, 'tan': tan,
478
+ })
479
+
480
+ try:
481
+ if '=' in cleaned:
482
+ lhs_str, rhs_str = cleaned.split('=', 1)
483
+ lhs = parse_expr(lhs_str.strip(), local_dict=local_dict)
484
+ rhs = parse_expr(rhs_str.strip(), local_dict=local_dict)
485
+ ode_eq = Eq(lhs, rhs)
486
+ else:
487
+ expr = parse_expr(cleaned.strip(), local_dict=local_dict)
488
+ ode_eq = Eq(expr, 0)
489
+
490
+ sol = dsolve(ode_eq, y(x))
491
+ return (
492
+ f"ODE: {ode_eq}\nGeneral solution: {sol}",
493
+ latex(sol),
494
+ )
495
+ except Exception as exc:
496
+ raise ValueError(f"Could not solve ODE: {exc}")
497
+
498
+
499
+ def _handle_eigenvalue(text: str) -> Tuple[str, str]:
500
+ from sympy import latex
501
+
502
+ mat = _parse_matrix(text)
503
+ eigs = mat.eigenvals()
504
+ evecs = mat.eigenvects()
505
+
506
+ eig_str = "; ".join(
507
+ f"Ξ»={ev} (multiplicity {mult})" for ev, mult in eigs.items()
508
+ )
509
+ evec_parts = []
510
+ for ev, mult, vecs in evecs:
511
+ for v in vecs:
512
+ evec_parts.append(f"Ξ»={ev}: {v.T.tolist()}")
513
+ evec_str = "; ".join(evec_parts)
514
+
515
+ result_str = f"Eigenvalues: {eig_str}\nEigenvectors: {evec_str}"
516
+ return (result_str, eig_str)
517
+
518
+
519
+ def _handle_determinant(text: str) -> Tuple[str, str]:
520
+ from sympy import latex
521
+
522
+ mat = _parse_matrix(text)
523
+ det = mat.det()
524
+ return (f"det = {det}", latex(det))
525
+
526
+
527
+ def _handle_inverse(text: str) -> Tuple[str, str]:
528
+ from sympy import latex
529
+
530
+ mat = _parse_matrix(text)
531
+ inv = mat.inv()
532
+ return (f"Inverse matrix:\n{inv}", latex(inv))
533
+
534
+
535
+ def _handle_matrix_rank(text: str) -> Tuple[str, str]:
536
+ mat = _parse_matrix(text)
537
+ rank = mat.rank()
538
+ return (f"Rank = {rank}", str(rank))
539
+
540
+
541
+ def _handle_matrix_trace(text: str) -> Tuple[str, str]:
542
+ from sympy import latex
543
+ mat = _parse_matrix(text)
544
+ trace = mat.trace()
545
+ return (f"Trace = {trace}", latex(trace))
546
+
547
+
548
+ def _handle_series(text: str) -> Tuple[str, str]:
549
+ from sympy import series, symbols, latex, oo
550
+
551
+ var_name = _extract_variable(text)
552
+ var = symbols(var_name)
553
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["series"])
554
+ # Strip leading "of" left after prefix removal
555
+ expr_text = re.sub(r'^of\s+', '', expr_text, flags=re.I).strip()
556
+
557
+ point = 0
558
+ m_point = re.search(r'(?:around|at|about|near)\s+([\w\.\-\+]+)', expr_text, re.I)
559
+ if m_point:
560
+ raw = m_point.group(1).replace("infinity", "oo").replace("∞", "oo")
561
+ point = oo if raw == "oo" else _parse(raw)
562
+ expr_text = expr_text[:m_point.start()].strip()
563
+
564
+ order = 6
565
+ m_order = re.search(r'(?:order|degree|up\s+to|terms?)\s+(\d+)', expr_text, re.I)
566
+ if m_order:
567
+ order = int(m_order.group(1))
568
+ expr_text = (expr_text[:m_order.start()] + expr_text[m_order.end():]).strip()
569
+
570
+ expr = _parse(expr_text)
571
+ result = series(expr, var, point, n=order)
572
+ return (
573
+ f"Series of {expr} around {var_name}={point} (order {order}): {result}",
574
+ latex(result),
575
+ )
576
+
577
+
578
+ def _handle_laplace(text: str) -> Tuple[str, str]:
579
+ from sympy import symbols, laplace_transform, latex
580
+
581
+ t, s = symbols('t s', positive=True)
582
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["laplace"])
583
+ expr_text = re.sub(r'\bof\b', '', expr_text, flags=re.I).strip()
584
+
585
+ expr = _parse(expr_text)
586
+ # With noconds=True SymPy returns the expression directly (not a tuple)
587
+ raw = laplace_transform(expr, t, s, noconds=True)
588
+ # Guard: some SymPy versions return a 3-tuple even with noconds=True
589
+ if isinstance(raw, tuple):
590
+ result = raw[0]
591
+ else:
592
+ result = raw
593
+ return (
594
+ f"L{{{expr}}} = {result}",
595
+ latex(result),
596
+ )
597
+
598
+
599
+ def _handle_inverse_laplace(text: str) -> Tuple[str, str]:
600
+ from sympy import symbols, inverse_laplace_transform, latex, Symbol
601
+
602
+ # SymPy requires s to be declared positive for inverse Laplace
603
+ t_pos, s_pos = symbols('t s', positive=True)
604
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["inverse_laplace"])
605
+ expr_text = re.sub(r'\bof\b', '', expr_text, flags=re.I).strip()
606
+ expr = _parse(expr_text)
607
+ # Substitute any plain 's' or 't' with the positive versions
608
+ s_plain = Symbol('s')
609
+ t_plain = Symbol('t')
610
+ expr = expr.subs([(s_plain, s_pos), (t_plain, t_pos)])
611
+ result = inverse_laplace_transform(expr, s_pos, t_pos)
612
+ return (
613
+ f"L⁻¹{{{expr}}} = {result}",
614
+ latex(result),
615
+ )
616
+
617
+
618
+ def _handle_fourier(text: str) -> Tuple[str, str]:
619
+ from sympy import symbols, fourier_transform, latex
620
+
621
+ x, k = symbols('x k')
622
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["fourier"])
623
+ expr_text = re.sub(r'\bof\b', '', expr_text, flags=re.I).strip()
624
+ expr = _parse(expr_text)
625
+ result = fourier_transform(expr, x, k)
626
+ return (
627
+ f"F{{{expr}}} = {result}",
628
+ latex(result),
629
+ )
630
+
631
+
632
+ def _handle_simplify(text: str) -> Tuple[str, str]:
633
+ from sympy import simplify, latex
634
+
635
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["simplify"])
636
+ expr = _parse(expr_text)
637
+ result = simplify(expr)
638
+ return (f"Simplified: {result}", latex(result))
639
+
640
+
641
+ def _handle_trig_simplify(text: str) -> Tuple[str, str]:
642
+ from sympy import trigsimp, latex
643
+
644
+ # strip any trig-specific prefix then fall through
645
+ expr_text = re.sub(
646
+ r'simplif[y]?\s+(?:the\s+)?trigonometric\s+|trig\s+simplif[y]?\s+|simplif[y]?\s+trig\s+',
647
+ '', text, flags=re.I
648
+ ).strip()
649
+ expr = _parse(expr_text)
650
+ result = trigsimp(expr)
651
+ return (f"Trig-simplified: {result}", latex(result))
652
+
653
+
654
+ def _handle_factor(text: str) -> Tuple[str, str]:
655
+ from sympy import factor, latex
656
+
657
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["factor"])
658
+ expr = _parse(expr_text)
659
+ result = factor(expr)
660
+ return (f"Factored: {result}", latex(result))
661
+
662
+
663
+ def _handle_expand(text: str) -> Tuple[str, str]:
664
+ from sympy import expand, latex
665
+
666
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["expand"])
667
+ expr = _parse(expr_text)
668
+ result = expand(expr)
669
+ return (f"Expanded: {result}", latex(result))
670
+
671
+
672
+ def _handle_partial_fraction(text: str) -> Tuple[str, str]:
673
+ from sympy import apart, symbols, latex
674
+
675
+ var_name = _extract_variable(text)
676
+ var = symbols(var_name)
677
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["partial_fraction"])
678
+ expr = _parse(expr_text)
679
+ result = apart(expr, var)
680
+ return (f"Partial fractions of {expr}: {result}", latex(result))
681
+
682
+
683
+ def _handle_gcd(text: str) -> Tuple[str, str]:
684
+ from sympy import gcd, latex
685
+
686
+ # Extract numbers from text
687
+ numbers = re.findall(r'\d+', text)
688
+ if len(numbers) < 2:
689
+ raise ValueError("Please provide at least two numbers. Example: GCD of 48 and 18")
690
+ from sympy import Integer
691
+ result = Integer(numbers[0])
692
+ for n in numbers[1:]:
693
+ result = gcd(result, Integer(n))
694
+ nums_str = ", ".join(numbers)
695
+ return (f"GCD({nums_str}) = {result}", latex(result))
696
+
697
+
698
+ def _handle_lcm(text: str) -> Tuple[str, str]:
699
+ from sympy import lcm, latex
700
+
701
+ numbers = re.findall(r'\d+', text)
702
+ if len(numbers) < 2:
703
+ raise ValueError("Please provide at least two numbers. Example: LCM of 12 and 18")
704
+ from sympy import Integer
705
+ result = Integer(numbers[0])
706
+ for n in numbers[1:]:
707
+ result = lcm(result, Integer(n))
708
+ nums_str = ", ".join(numbers)
709
+ return (f"LCM({nums_str}) = {result}", latex(result))
710
+
711
+
712
+ def _handle_prime_factors(text: str) -> Tuple[str, str]:
713
+ from sympy import factorint, latex
714
+
715
+ numbers = re.findall(r'\d+', text)
716
+ if not numbers:
717
+ raise ValueError("Please provide a number. Example: prime factorization of 360")
718
+ n = int(numbers[0])
719
+ factors = factorint(n)
720
+ factor_str = " Γ— ".join(
721
+ f"{p}^{e}" if e > 1 else str(p) for p, e in sorted(factors.items())
722
+ )
723
+ return (f"{n} = {factor_str}", factor_str)
724
+
725
+
726
+ def _handle_modular(text: str) -> Tuple[str, str]:
727
+ from sympy import mod_inverse, Integer
728
+
729
+ # modular inverse: "modular inverse of A mod M"
730
+ m_inv = re.search(
731
+ r'modular\s+inverse\s+of\s+(\d+)\s+mod\s+(\d+)', text, re.I
732
+ )
733
+ if m_inv:
734
+ a, m_val = int(m_inv.group(1)), int(m_inv.group(2))
735
+ inv = mod_inverse(a, m_val)
736
+ return (f"Modular inverse of {a} mod {m_val} = {inv}", str(inv))
737
+
738
+ # plain modulo: "A mod B"
739
+ m_mod = re.search(r'(\d+)\s+mod(?:ulo)?\s+(\d+)', text, re.I)
740
+ if m_mod:
741
+ a, m_val = int(m_mod.group(1)), int(m_mod.group(2))
742
+ result = a % m_val
743
+ return (f"{a} mod {m_val} = {result}", str(result))
744
+
745
+ raise ValueError(
746
+ "Could not parse modular arithmetic. "
747
+ "Try: '17 mod 5' or 'modular inverse of 3 mod 7'"
748
+ )
749
+
750
+
751
+ def _handle_statistics(text: str) -> Tuple[str, str]:
752
+ from sympy.stats import Normal
753
+ from sympy import Rational, latex
754
+
755
+ # Extract list of numbers from text
756
+ numbers = re.findall(r'-?\d+(?:\.\d+)?', text)
757
+ if not numbers:
758
+ raise ValueError(
759
+ "Please provide a list of numbers. Example: mean of 2, 4, 6, 8"
760
+ )
761
+ vals = [float(n) for n in numbers]
762
+ n = len(vals)
763
+ mean = sum(vals) / n
764
+ sorted_vals = sorted(vals)
765
+ if n % 2 == 0:
766
+ median = (sorted_vals[n//2 - 1] + sorted_vals[n//2]) / 2
767
+ else:
768
+ median = sorted_vals[n//2]
769
+ variance = sum((v - mean) ** 2 for v in vals) / n
770
+ std_dev = variance ** 0.5
771
+
772
+ result_str = (
773
+ f"Data: {vals}\n"
774
+ f"Mean = {mean:.6g}\n"
775
+ f"Median = {median:.6g}\n"
776
+ f"Variance = {variance:.6g}\n"
777
+ f"Std Dev = {std_dev:.6g}"
778
+ )
779
+ return (result_str, result_str.replace("\n", r" \\ "))
780
+
781
+
782
+ def _handle_factorial(text: str) -> Tuple[str, str]:
783
+ from sympy import factorial, latex, Integer
784
+
785
+ numbers = re.findall(r'\d+', text)
786
+ if not numbers:
787
+ raise ValueError("Please provide a number. Example: factorial of 10")
788
+ n = int(numbers[0])
789
+ if n > 1000:
790
+ raise ValueError("Number too large for factorial (max 1000)")
791
+ result = factorial(Integer(n))
792
+ return (f"{n}! = {result}", latex(result))
793
+
794
+
795
+ def _handle_binomial(text: str) -> Tuple[str, str]:
796
+ from sympy import binomial as sym_binomial, latex, Integer
797
+
798
+ numbers = re.findall(r'\d+', text)
799
+ if len(numbers) < 2:
800
+ raise ValueError("Please provide n and r. Example: binomial coefficient 10 choose 3")
801
+ n, r = int(numbers[0]), int(numbers[1])
802
+ result = sym_binomial(Integer(n), Integer(r))
803
+ return (f"C({n}, {r}) = {result}", latex(result))
804
+
805
+
806
+ def _handle_permutation(text: str) -> Tuple[str, str]:
807
+ from sympy import factorial, latex, Integer
808
+
809
+ numbers = re.findall(r'\d+', text)
810
+ if len(numbers) < 2:
811
+ raise ValueError("Please provide n and r. Example: permutation 10 P 3")
812
+ n, r = int(numbers[0]), int(numbers[1])
813
+ result = factorial(Integer(n)) // factorial(Integer(n - r))
814
+ return (f"P({n}, {r}) = {result}", latex(result))
815
+
816
+
817
+ def _handle_summation(text: str) -> Tuple[str, str]:
818
+ from sympy import summation, symbols, oo, latex
819
+
820
+ # Try to detect summation variable from "for X=" or "for X from" pattern
821
+ m_var = re.search(r'\bfor\s+([a-zA-Z])\s*(?:=|from)\b', text, re.I)
822
+ if m_var:
823
+ var_name = m_var.group(1)
824
+ else:
825
+ var_name = _extract_variable(text, default="k")
826
+ var = symbols(var_name)
827
+
828
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["summation"])
829
+ # Strip leading "of" left after prefix removal
830
+ expr_text = re.sub(r'^of\s+', '', expr_text, flags=re.I).strip()
831
+
832
+ # Pattern A: "EXPR for k=A to B" or "EXPR for k from A to B"
833
+ m = re.search(
834
+ rf'(.*?)\s+for\s+{var_name}\s*(?:=|from)\s*(-?[\w\.]+)\s+to\s+(-?[\w\.∞]+)',
835
+ expr_text, re.I
836
+ )
837
+ # Pattern B: "EXPR from k=A to B"
838
+ if not m:
839
+ m = re.search(
840
+ rf'(.*?)\s+from\s+{var_name}\s*=\s*(-?[\w\.]+)\s+to\s+(-?[\w\.∞]+)',
841
+ expr_text, re.I
842
+ )
843
+
844
+ def _parse_bound(raw: str):
845
+ raw = raw.replace("infinity", "oo").replace("∞", "oo").replace("infty", "oo")
846
+ if raw == "oo": return oo
847
+ if raw == "-oo": return -oo
848
+ return _parse(raw)
849
+
850
+ if m:
851
+ expr_raw = m.group(1).strip()
852
+ lo = _parse_bound(m.group(2))
853
+ hi = _parse_bound(m.group(3))
854
+ expr = _parse(expr_raw)
855
+ result = summation(expr, (var, lo, hi))
856
+ return (
857
+ f"Ξ£({expr}, {var_name}={lo}..{hi}) = {result}",
858
+ latex(result),
859
+ )
860
+ else:
861
+ expr = _parse(expr_text)
862
+ result = summation(expr, (var, 0, oo))
863
+ return (
864
+ f"Σ({expr}, {var_name}=0..∞) = {result}",
865
+ latex(result),
866
+ )
867
+
868
+
869
+ def _handle_product(text: str) -> Tuple[str, str]:
870
+ from sympy import Product, symbols, oo, latex
871
+
872
+ var_name = _extract_variable(text, default="k")
873
+ var = symbols(var_name)
874
+
875
+ expr_text = _strip_prefix(text, _ADVANCED_OPS["product"])
876
+ m = re.search(
877
+ rf'(.*?)\s+(?:for|from)\s+{var_name}\s*=\s*(-?\w+)\s+to\s+(-?\w+)',
878
+ expr_text, re.I
879
+ )
880
+ if m:
881
+ expr_raw = m.group(1).strip()
882
+ lo_raw = m.group(2).replace("infty", "oo")
883
+ hi_raw = m.group(3).replace("infty", "oo")
884
+ expr = _parse(expr_raw)
885
+ lo = oo if lo_raw == "oo" else _parse(lo_raw)
886
+ hi = oo if hi_raw == "oo" else _parse(hi_raw)
887
+ result = Product(expr, (var, lo, hi)).doit()
888
+ return (
889
+ f"∏({expr}, {var_name}={lo}..{hi}) = {result}",
890
+ latex(result),
891
+ )
892
+ else:
893
+ expr = _parse(expr_text)
894
+ result = Product(expr, (var, 1, oo)).doit()
895
+ return (
896
+ f"∏({expr}, {var_name}=1..∞) = {result}",
897
+ latex(result),
898
+ )
899
+
900
+
901
+ def _handle_complex_ops(text: str) -> Tuple[str, str]:
902
+ from sympy import re as Re, im as Im, Abs, arg, conjugate, latex, symbols, I
903
+
904
+ # Try to extract a complex expression
905
+ # Strip common prefixes
906
+ clean = re.sub(
907
+ r'(?:real\s+part\s+of|imaginary\s+part\s+of|modulus\s+of|argument\s+of|conjugate\s+of|complex\s+number)\s*',
908
+ '', text, flags=re.I
909
+ ).strip()
910
+
911
+ expr = _parse(clean)
912
+
913
+ results = {
914
+ "Real part": Re(expr),
915
+ "Imaginary part": Im(expr),
916
+ "Modulus": Abs(expr),
917
+ "Argument": arg(expr),
918
+ "Conjugate": conjugate(expr),
919
+ }
920
+
921
+ lines = [f"{k} = {v}" for k, v in results.items()]
922
+ result_str = "\n".join(lines)
923
+ result_latex = r" \\ ".join(f"{k} = {latex(v)}" for k, v in results.items())
924
+ return (result_str, result_latex)
925
+
926
+
927
+ # ─────────────────────────────────────────────────────────────────────────────
928
+ # Handler dispatch table
929
+ # ─────────────────────────────────────────────────────────────────────────────
930
+
931
+ _HANDLERS = {
932
+ "integrate": _handle_integrate,
933
+ "differentiate": _handle_differentiate,
934
+ "limit": _handle_limit,
935
+ "solve": _handle_solve,
936
+ "ode": _handle_ode,
937
+ "series": _handle_series,
938
+ "laplace": _handle_laplace,
939
+ "inverse_laplace": _handle_inverse_laplace,
940
+ "fourier": _handle_fourier,
941
+ "simplify": _handle_simplify,
942
+ "trig_simplify": _handle_trig_simplify,
943
+ "factor": _handle_factor,
944
+ "expand": _handle_expand,
945
+ "partial_fraction": _handle_partial_fraction,
946
+ "eigenvalue": _handle_eigenvalue,
947
+ "determinant": _handle_determinant,
948
+ "inverse": _handle_inverse,
949
+ "matrix_rank": _handle_matrix_rank,
950
+ "matrix_trace": _handle_matrix_trace,
951
+ "gcd": _handle_gcd,
952
+ "lcm": _handle_lcm,
953
+ "prime_factors": _handle_prime_factors,
954
+ "modular": _handle_modular,
955
+ "statistics": _handle_statistics,
956
+ "factorial": _handle_factorial,
957
+ "binomial": _handle_binomial,
958
+ "permutation": _handle_permutation,
959
+ "summation": _handle_summation,
960
+ "product": _handle_product,
961
+ "complex_ops": _handle_complex_ops,
962
+ }
963
+
964
+
965
+ # ─────────────────────────────────────────────────────────────────────────────
966
+ # Public interface
967
+ # ─────────────────────────────────────────────────────────────────────────────
968
+
969
+ def solve(user_input: str) -> Tuple[bool, str, str]:
970
+ """
971
+ Main entry point for the advanced math engine.
972
+
973
+ Args:
974
+ user_input: Natural language math query.
975
+
976
+ Returns:
977
+ (success, result_str, latex_str)
978
+ success – True if SymPy computed an answer
979
+ result_str – Human-readable answer
980
+ latex_str – LaTeX of the result
981
+ """
982
+ op = detect_advanced_operation(user_input)
983
+ if op is None:
984
+ return (False, "", "")
985
+
986
+ handler = _HANDLERS.get(op)
987
+ if handler is None:
988
+ return (False, f"Operation '{op}' recognised but not yet implemented.", "")
989
+
990
+ try:
991
+ result_str, latex_str = handler(user_input)
992
+ return (True, result_str, latex_str)
993
+ except Exception as exc:
994
+ return (False, f"Math engine error ({op}): {exc}", "")
anveshai_memory.db ADDED
Binary file (12.3 kB). View file
 
conversation.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Conversation patterns for AnveshAI Edge Prototype
2
+ # Format: PATTERN|||RESPONSE
3
+ # Patterns are case-insensitive regex, separated from responses by |||
4
+
5
+ hello|hi|hey|||Hello! I'm AnveshAI Edge. How can I help you today?
6
+ how are you|how do you do|||I'm running smoothly and ready to assist you!
7
+ what is your name|who are you|what are you|||I'm AnveshAI Edge Prototype, a local offline AI assistant built on a hierarchical modular architecture.
8
+ what can you do|help me|what do you know|||I can answer knowledge questions, solve math problems, and chat with you β€” all locally without any internet connection! Type /help for commands.
9
+ thank you|thanks|thank u|||You're welcome! Is there anything else I can help you with?
10
+ bye|goodbye|see you|quit|exit|||Goodbye! Use /exit to close the session properly.
11
+ good morning|||Good morning! Ready to assist you today.
12
+ good evening|||Good evening! What can I help you with?
13
+ good night|||Good night! Hope I was helpful today.
14
+ who made you|who created you|who built you|||I was built as a demo of a hierarchical offline AI architecture. Designed to be modular and fully offline.
15
+ are you smart|are you intelligent|||I'm a lightweight rule-based system β€” not as powerful as a large language model, but fast, private, and fully offline!
16
+ what time is it|current time|||I don't have access to a clock, but you can check your system time easily.
17
+ tell me a joke|joke|||Why do programmers prefer dark mode? Because light attracts bugs!
18
+ what is ai|what is artificial intelligence|||Artificial Intelligence is the simulation of human intelligence in machines. Type 'knowledge: artificial intelligence' for a detailed explanation!
19
+ interesting|cool|awesome|wow|||Glad you think so! What else would you like to explore?
20
+ okay|ok|alright|||Sure! Let me know if there's anything I can help you with.
21
+ yes|||Got it! What would you like to do next?
22
+ no|||Understood. Feel free to ask me anything else.
23
+ sorry|my bad|||No problem at all! How can I help you?
conversation_engine.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Conversation Engine β€” handles general chat using pattern-response pairs.
3
+
4
+ How it works:
5
+ 1. Loads 'conversation.txt' at startup.
6
+ Each non-comment line format: PATTERN|||RESPONSE
7
+ 2. Tries to match user input against compiled regex patterns.
8
+ 3. Returns (response, matched):
9
+ matched = True β†’ a pattern fired; use the response directly
10
+ matched = False β†’ no match; caller should escalate to LLM fallback
11
+
12
+ EXTENSION POINT: The respond() method can be replaced wholesale with an LLM
13
+ call without touching any other module. Contract: (str) β†’ (str, bool).
14
+ """
15
+
16
+ import os
17
+ import re
18
+ from typing import List, Tuple
19
+
20
+
21
+ CONVERSATION_FILE = os.path.join(os.path.dirname(__file__), "conversation.txt")
22
+
23
+
24
+ def _load_patterns(filepath: str) -> List[Tuple[re.Pattern, str]]:
25
+ patterns: List[Tuple[re.Pattern, str]] = []
26
+ if not os.path.exists(filepath):
27
+ return patterns
28
+ with open(filepath, "r", encoding="utf-8") as f:
29
+ for line in f:
30
+ line = line.strip()
31
+ if not line or line.startswith("#"):
32
+ continue
33
+ if "|||" not in line:
34
+ continue
35
+ pattern_part, response_part = line.split("|||", 1)
36
+ pattern_str = pattern_part.strip()
37
+ response_str = response_part.strip()
38
+ if not pattern_str or not response_str:
39
+ continue
40
+ try:
41
+ compiled = re.compile(pattern_str, re.IGNORECASE)
42
+ patterns.append((compiled, response_str))
43
+ except re.error:
44
+ continue
45
+ return patterns
46
+
47
+
48
+ class ConversationEngine:
49
+ """Rule-based pattern-matching chat engine backed by conversation.txt."""
50
+
51
+ def __init__(self, conversation_file: str = CONVERSATION_FILE):
52
+ self.patterns = _load_patterns(conversation_file)
53
+
54
+ def respond(self, user_input: str) -> Tuple[str, bool]:
55
+ """
56
+ Match user input against stored patterns.
57
+
58
+ Returns:
59
+ (response, matched)
60
+ matched = True β†’ pattern found, response is ready to use
61
+ matched = False β†’ no pattern matched; escalate to LLM
62
+ """
63
+ text = user_input.strip()
64
+ for pattern, response in self.patterns:
65
+ if pattern.search(text):
66
+ return (response, True)
67
+ # Signal: no rule matched β€” let the LLM handle it
68
+ return ("", False)
knowledge.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Python is a high-level, interpreted programming language known for its readability and simplicity. It supports multiple programming paradigms, including procedural, object-oriented, and functional programming. Python was created by Guido van Rossum and first released in 1991. It is widely used in web development, data science, artificial intelligence, and automation.
2
+
3
+ Machine learning is a subset of artificial intelligence that enables systems to learn from data and improve their performance over time without being explicitly programmed. Common machine learning algorithms include linear regression, decision trees, neural networks, and support vector machines. Libraries like scikit-learn, TensorFlow, and PyTorch are widely used for machine learning in Python.
4
+
5
+ Artificial intelligence (AI) refers to the simulation of human intelligence in machines that are programmed to think and learn. AI encompasses subfields such as machine learning, natural language processing, computer vision, and robotics. AI applications include virtual assistants, recommendation systems, autonomous vehicles, and medical diagnosis.
6
+
7
+ Natural language processing (NLP) is a branch of AI that helps computers understand, interpret, and generate human language. Key NLP tasks include tokenization, sentiment analysis, named entity recognition, and machine translation. Popular NLP libraries include NLTK, spaCy, and Hugging Face Transformers.
8
+
9
+ Deep learning is a subset of machine learning that uses neural networks with many layers (deep neural networks) to model complex patterns in data. It has achieved breakthrough performance in image recognition, speech recognition, and natural language understanding. Deep learning requires large datasets and significant computational resources.
10
+
11
+ A database is an organized collection of structured information or data, typically stored electronically in a computer system. Databases are managed by database management systems (DBMS). Common types include relational databases (SQL) like MySQL, PostgreSQL, and SQLite, and non-relational databases (NoSQL) like MongoDB and Redis.
12
+
13
+ The internet is a global network of interconnected computers that communicate using standardized protocols. It was developed in the late 1960s by ARPA (Advanced Research Projects Agency). The World Wide Web (WWW), invented by Tim Berners-Lee in 1989, is a service that runs on the internet and allows users to access websites using a browser.
14
+
15
+ Cloud computing is the delivery of computing services including servers, storage, databases, networking, software, analytics, and intelligence over the internet to offer faster innovation, flexible resources, and economies of scale. Major cloud providers include Amazon Web Services (AWS), Microsoft Azure, and Google Cloud Platform.
16
+
17
+ Linux is an open-source operating system kernel first released by Linus Torvalds in 1991. It forms the basis of many operating systems (distributions) such as Ubuntu, Fedora, and Debian. Linux is widely used in servers, supercomputers, Android devices, and embedded systems. It is known for its stability, security, and flexibility.
18
+
19
+ Cybersecurity refers to the practice of protecting systems, networks, and programs from digital attacks. These attacks often aim to access, change, or destroy sensitive information, extort money, or interrupt normal business processes. Key concepts include encryption, firewalls, intrusion detection, and ethical hacking.
20
+
21
+ Quantum computing is a type of computation that harnesses quantum mechanical phenomena such as superposition and entanglement. Unlike classical bits (0 or 1), quantum bits (qubits) can represent multiple states simultaneously. This allows quantum computers to solve certain problems exponentially faster than classical computers.
22
+
23
+ Blockchain is a distributed ledger technology that records transactions across many computers so that the record cannot be altered retroactively. It was popularized by Bitcoin, the first decentralized cryptocurrency, created by the pseudonymous Satoshi Nakamoto in 2008. Blockchain is also used in smart contracts, supply chain management, and digital identity.
24
+
25
+ An algorithm is a step-by-step procedure or formula for solving a problem. In computer science, algorithms are used to process data, perform calculations, and automate reasoning tasks. Common categories include sorting algorithms (quicksort, mergesort), search algorithms (binary search, BFS, DFS), and graph algorithms (Dijkstra, A*).
26
+
27
+ Data structures are ways of organizing and storing data in a computer so that it can be accessed and modified efficiently. Common data structures include arrays, linked lists, stacks, queues, trees, heaps, hash tables, and graphs. Choosing the right data structure is fundamental to writing efficient algorithms.
28
+
29
+ Version control is a system that records changes to files over time so you can recall specific versions later. Git is the most widely used version control system. Platforms like GitHub, GitLab, and Bitbucket host Git repositories and provide collaboration tools such as pull requests, issues, and CI/CD pipelines.
knowledge_engine.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Knowledge Engine β€” retrieves relevant information from a local knowledge base.
3
+
4
+ How it works:
5
+ 1. Loads 'knowledge.txt' at startup (one paragraph per blank-line block).
6
+ 2. For a given query, scores each paragraph using keyword overlap.
7
+ 3. Returns the highest-scoring paragraph + a boolean indicating confidence.
8
+ If confidence is low, the caller (main.py) will escalate to the LLM.
9
+
10
+ This is intentionally lightweight and fully offline. In the future it can be
11
+ swapped for a vector-based retrieval system (FAISS + sentence-transformers)
12
+ without changing the rest of the architecture.
13
+ """
14
+
15
+ import os
16
+ import re
17
+ from typing import List, Tuple
18
+
19
+
20
+ KNOWLEDGE_FILE = os.path.join(os.path.dirname(__file__), "knowledge.txt")
21
+
22
+ # A paragraph must score at least this much to be considered a real match.
23
+ # Queries below this score are escalated to the LLM fallback.
24
+ MIN_RELEVANCE_SCORE = 2
25
+
26
+ STOP_WORDS = {
27
+ "a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
28
+ "have", "has", "had", "do", "does", "did", "will", "would", "shall",
29
+ "should", "may", "might", "must", "can", "could", "to", "of", "in",
30
+ "on", "at", "by", "for", "with", "about", "against", "between", "into",
31
+ "through", "during", "before", "after", "above", "below", "from",
32
+ "up", "down", "out", "off", "over", "under", "again", "and", "but",
33
+ "or", "nor", "so", "yet", "both", "either", "neither", "not", "no",
34
+ "what", "which", "who", "whom", "this", "that", "these", "those",
35
+ "i", "me", "my", "myself", "we", "our", "you", "your", "he", "she",
36
+ "it", "they", "them", "their", "tell", "explain", "describe", "give",
37
+ "me", "some", "information", "about",
38
+ }
39
+
40
+
41
+ def _load_paragraphs(filepath: str) -> List[str]:
42
+ if not os.path.exists(filepath):
43
+ return []
44
+ with open(filepath, "r", encoding="utf-8") as f:
45
+ content = f.read()
46
+ raw = re.split(r"\n\s*\n", content.strip())
47
+ return [p.strip() for p in raw if p.strip()]
48
+
49
+
50
+ def _tokenize(text: str) -> List[str]:
51
+ words = re.findall(r"\b[a-z]+\b", text.lower())
52
+ return [w for w in words if w not in STOP_WORDS and len(w) > 2]
53
+
54
+
55
+ def _score_paragraph(query_tokens: List[str], paragraph: str) -> int:
56
+ para_lower = paragraph.lower()
57
+ score = 0
58
+ for token in query_tokens:
59
+ if re.search(r"\b" + re.escape(token) + r"\b", para_lower):
60
+ score += 2
61
+ elif token in para_lower:
62
+ score += 1
63
+ return score
64
+
65
+
66
+ def _strip_knowledge_prefixes(text: str) -> str:
67
+ prefixes = [
68
+ "what is", "what are", "who is", "who are", "explain", "define",
69
+ "tell me about", "describe", "how does", "why is", "when was",
70
+ "where is", "history of", "meaning of", "knowledge:", "knowledge :",
71
+ "learn about", "facts about", "information about",
72
+ ]
73
+ lowered = text.lower().strip()
74
+ for prefix in prefixes:
75
+ if lowered.startswith(prefix):
76
+ return text[len(prefix):].strip()
77
+ return text
78
+
79
+
80
+ class KnowledgeEngine:
81
+ """Local keyword-scored knowledge retrieval over knowledge.txt."""
82
+
83
+ def __init__(self, knowledge_file: str = KNOWLEDGE_FILE):
84
+ self.paragraphs: List[str] = _load_paragraphs(knowledge_file)
85
+ self._loaded = len(self.paragraphs) > 0
86
+
87
+ def is_loaded(self) -> bool:
88
+ return self._loaded
89
+
90
+ def query(self, user_input: str) -> Tuple[str, bool]:
91
+ """
92
+ Find the most relevant paragraph for the given query.
93
+
94
+ Returns:
95
+ (response, found)
96
+ found = True β†’ a high-confidence match was found in the KB
97
+ found = False β†’ no confident match; caller should try the LLM
98
+ """
99
+ if not self._loaded:
100
+ return (
101
+ "Knowledge base unavailable. Ensure 'knowledge.txt' exists.",
102
+ False,
103
+ )
104
+
105
+ clean_query = _strip_knowledge_prefixes(user_input)
106
+ query_tokens = _tokenize(clean_query)
107
+
108
+ if not query_tokens:
109
+ return ("Could you rephrase? I couldn't parse the query.", False)
110
+
111
+ scored: List[Tuple[int, str]] = [
112
+ (_score_paragraph(query_tokens, para), para)
113
+ for para in self.paragraphs
114
+ ]
115
+
116
+ best_score, best_para = max(scored, key=lambda x: x[0])
117
+
118
+ if best_score < MIN_RELEVANCE_SCORE:
119
+ # Signal to caller: escalate to LLM
120
+ return ("", False)
121
+
122
+ return (best_para, True)
llm_engine.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LLM Engine β€” local Qwen2.5-0.5B-Instruct fallback via llama-cpp-python.
3
+
4
+ This is the bottom layer of the AnveshAI hierarchy:
5
+
6
+ Math β†’ math_engine (instant, rule-based)
7
+ Knowledge β†’ knowledge_engine (keyword retrieval from knowledge.txt)
8
+ └─ no match β†’ LLMEngine.generate (Qwen2.5-0.5B)
9
+ Conversation β†’ conversation_engine (pattern matching from conversation.txt)
10
+ └─ no match β†’ LLMEngine.generate (Qwen2.5-0.5B)
11
+
12
+ Model: Qwen/Qwen2.5-0.5B-Instruct (Q4_K_M GGUF, ~350 MB)
13
+ ─ Best-in-class quality at 0.5B parameters
14
+ ─ Runs entirely on CPU via llama.cpp
15
+ ─ Downloaded once into ~/.cache/huggingface/ on first use
16
+ ─ Loaded LAZILY: the model only loads when first needed,
17
+ keeping startup instant.
18
+ """
19
+
20
+ MODEL_REPO = "Qwen/Qwen2.5-0.5B-Instruct-GGUF"
21
+ MODEL_FILE = "qwen2.5-0.5b-instruct-q4_k_m.gguf"
22
+
23
+ SYSTEM_PROMPT = (
24
+ "You are AnveshAI Edge, a helpful offline AI assistant. "
25
+ "Answer questions thoroughly and completely. Show full working steps "
26
+ "for math or technical questions. Do not repeat the question back. "
27
+ "If you are unsure about something, say so clearly."
28
+ )
29
+
30
+ MATH_SYSTEM_PROMPT = (
31
+ "You are a mathematics tutor. "
32
+ "You will be given a VERIFIED ANSWER computed by a symbolic engine. "
33
+ "That answer is 100% correct β€” do NOT change it, do NOT recompute it. "
34
+ "Your ONLY job is to explain, step by step, HOW a student would work through "
35
+ "the problem and arrive at that exact answer. "
36
+ "Every step must lead logically toward the verified answer. "
37
+ "State the verified answer word-for-word at the end of your explanation."
38
+ )
39
+
40
+ MAX_TOKENS = 1024 # enough for detailed explanations and step-by-step answers
41
+ TEMPERATURE = 0.7
42
+ MATH_TEMPERATURE = 0.1 # near-deterministic for math explanations
43
+ TOP_P = 0.9
44
+ N_CTX = 16384 # match model's trained context (supports up to 32768)
45
+
46
+
47
+ class LLMEngine:
48
+ """
49
+ Lazy-loading wrapper around Qwen2.5-0.5B-Instruct (GGUF via llama.cpp).
50
+
51
+ Usage:
52
+ engine = LLMEngine()
53
+ response = engine.generate("What is photosynthesis?")
54
+
55
+ The GGUF model is downloaded from HuggingFace on the first call to
56
+ generate() and cached locally. Every subsequent call reuses the
57
+ in-memory model β€” no re-loading.
58
+ """
59
+
60
+ def __init__(self) -> None:
61
+ self._llm = None
62
+ self._loaded: bool = False
63
+ self._failed: bool = False
64
+ self._fail_reason: str = ""
65
+
66
+ def is_available(self) -> bool:
67
+ """True once the model has loaded without error."""
68
+ return self._loaded and not self._failed
69
+
70
+ # ------------------------------------------------------------------
71
+ # Internal helpers
72
+ # ------------------------------------------------------------------
73
+
74
+ def _load(self) -> None:
75
+ """Download (first run only) and load the GGUF model into memory."""
76
+ if self._loaded or self._failed:
77
+ return
78
+
79
+ try:
80
+ print(
81
+ f"\n [LLM] Loading {MODEL_FILE} … "
82
+ "(first run downloads ~350 MB, then cached locally)",
83
+ flush=True,
84
+ )
85
+
86
+ from llama_cpp import Llama
87
+
88
+ self._llm = Llama.from_pretrained(
89
+ repo_id=MODEL_REPO,
90
+ filename=MODEL_FILE,
91
+ n_ctx=N_CTX,
92
+ n_threads=4, # use up to 4 CPU threads
93
+ verbose=False,
94
+ )
95
+
96
+ self._loaded = True
97
+ print(" [LLM] Qwen2.5-0.5B-Instruct ready\n", flush=True)
98
+
99
+ except Exception as exc:
100
+ self._failed = True
101
+ self._fail_reason = str(exc)
102
+ print(f" [LLM] Failed to load: {exc}\n", flush=True)
103
+
104
+ # ------------------------------------------------------------------
105
+ # Public API
106
+ # ------------------------------------------------------------------
107
+
108
+ def generate(
109
+ self,
110
+ user_input: str,
111
+ context: str = "",
112
+ system_prompt: str = "",
113
+ temperature: float = TEMPERATURE,
114
+ ) -> str:
115
+ """
116
+ Generate a response using the local LLM.
117
+
118
+ Args:
119
+ user_input : The user's message or question.
120
+ context : Optional retrieved text to inject as background.
121
+ system_prompt : Override the default system prompt (e.g. for math).
122
+ temperature : Sampling temperature; use low values for math.
123
+
124
+ Returns:
125
+ The model's reply as a plain string.
126
+ """
127
+ self._load()
128
+
129
+ if self._failed:
130
+ return (
131
+ "The local LLM is currently unavailable "
132
+ f"({self._fail_reason}). "
133
+ "Ensure 'llama-cpp-python' is installed and the model "
134
+ "could be downloaded."
135
+ )
136
+
137
+ try:
138
+ system_content = system_prompt if system_prompt else SYSTEM_PROMPT
139
+ if context:
140
+ system_content += f"\n\nRelevant background:\n{context}"
141
+
142
+ messages = [
143
+ {"role": "system", "content": system_content},
144
+ {"role": "user", "content": user_input},
145
+ ]
146
+
147
+ output = self._llm.create_chat_completion(
148
+ messages=messages,
149
+ max_tokens=MAX_TOKENS,
150
+ temperature=temperature,
151
+ top_p=TOP_P,
152
+ )
153
+
154
+ response: str = output["choices"][0]["message"]["content"]
155
+ return response.strip()
156
+
157
+ except Exception as exc:
158
+ return f"LLM generation error: {exc}"
main.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AnveshAI Edge Prototype
3
+ =======================
4
+ Terminal-based offline AI assistant β€” hierarchical modular architecture
5
+ with chain-of-thought (CoT) reasoning.
6
+
7
+ Routing & reasoning chain:
8
+ /commands β†’ inline handler (instant)
9
+ Plain arithmetic β†’ math_engine (instant, AST-based)
10
+ Advanced math β†’ ReasoningEngine.analyze() (problem decomposition)
11
+ └─ advanced_math_engine (SymPy symbolic)
12
+ └─ ReasoningEngine.build_math_prompt()
13
+ β†’ LLM step-by-step explanation
14
+ Knowledge query β†’ ReasoningEngine.analyze() (CoT planning)
15
+ └─ knowledge_engine (local KB)
16
+ β”œβ”€ match found β†’ return KB paragraph
17
+ └─ no match β†’ ReasoningEngine.build_general_prompt()
18
+ β†’ LLM structured answer
19
+ Conversation β†’ conversation_engine (pattern rules)
20
+ └─ no pattern β†’ ReasoningEngine.build_general_prompt()
21
+ β†’ LLM structured answer
22
+
23
+ Commands:
24
+ /help β†’ list commands
25
+ /history β†’ last 10 interactions
26
+ /exit β†’ quit
27
+ """
28
+
29
+ import sys
30
+
31
+ # ── Optional colour support ───────────────────────────────────────────────────
32
+ try:
33
+ from colorama import init as colorama_init, Fore, Style
34
+ colorama_init(autoreset=True)
35
+ except ImportError:
36
+ class _NoColor:
37
+ def __getattr__(self, _): return ""
38
+ Fore = Style = _NoColor()
39
+
40
+ # ── Module imports ────────────────────────────────────────────────────────────
41
+ from router import classify_intent
42
+ from math_engine import evaluate as math_evaluate
43
+ from advanced_math_engine import solve as advanced_math_solve
44
+ from knowledge_engine import KnowledgeEngine
45
+ from conversation_engine import ConversationEngine
46
+ from llm_engine import LLMEngine, MATH_SYSTEM_PROMPT, MATH_TEMPERATURE
47
+ from reasoning_engine import ReasoningEngine
48
+ from memory import initialize_db, save_interaction, format_history
49
+
50
+
51
+ # ─────────────────────────────────────────────────────────────────────────────
52
+ BANNER = r"""
53
+ ╔══════════════════════════════════════════════════════╗
54
+ β•‘ ___ __ ___ ____ β•‘
55
+ β•‘ / _ | ___ _ _____ ___ / / / _ | / _/ β•‘
56
+ β•‘ / __ |/ _ \ |/ / -_|_-</ _ \/ __ |_/ / β•‘
57
+ β•‘ /_/ |_/_//_/___/\__/___/_//_/_/ |_/___/ EDGE β•‘
58
+ β•‘ β•‘
59
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
60
+ """
61
+
62
+ HELP_TEXT = """
63
+ Available commands:
64
+ /help β€” show this help message
65
+ /history β€” display last 10 conversation entries
66
+ /exit β€” quit AnveshAI Edge
67
+
68
+ How to use:
69
+ β€’ Advanced math β†’ symbolic engine computes the EXACT answer,
70
+ LLM explains step-by-step working
71
+
72
+ Calculus:
73
+ "integrate x^2 sin(x)"
74
+ "definite integral of x^2 from 0 to 3"
75
+ "derivative of x^3 + 2x"
76
+ "second derivative of sin(x) * e^x"
77
+ "limit of sin(x)/x as x approaches 0"
78
+
79
+ Algebra & equations:
80
+ "solve x^2 - 5x + 6 = 0"
81
+ "solve 2x + 3 = 7"
82
+
83
+ Differential equations:
84
+ "solve differential equation y'' + y = 0"
85
+ "solve ode dy/dx = y"
86
+
87
+ Series & transforms:
88
+ "taylor series of e^x around 0 order 6"
89
+ "laplace transform of sin(t)"
90
+ "inverse laplace of 1/(s^2 + 1)"
91
+ "fourier transform of exp(-x^2)"
92
+
93
+ Matrices:
94
+ "determinant of [[1,2],[3,4]]"
95
+ "inverse matrix [[2,1],[5,3]]"
96
+ "eigenvalue [[4,1],[2,3]]"
97
+ "rank of matrix [[1,2,3],[4,5,6]]"
98
+
99
+ Symbolic manipulation:
100
+ "factor x^3 - 8"
101
+ "simplify (x^2 - 1)/(x - 1)"
102
+ "expand (x + y)^4"
103
+ "partial fraction 1/(x^2 - 1)"
104
+
105
+ Number theory:
106
+ "gcd of 48 and 18"
107
+ "lcm of 12 and 15"
108
+ "prime factorization of 360"
109
+ "17 mod 5"
110
+ "modular inverse of 3 mod 7"
111
+
112
+ Statistics:
113
+ "mean of 2, 4, 6, 8, 10"
114
+ "standard deviation of 1, 2, 3, 4, 5"
115
+
116
+ Combinatorics:
117
+ "factorial of 10"
118
+ "binomial coefficient 10 choose 3"
119
+ "permutation 6 P 2"
120
+
121
+ Summations:
122
+ "sum of k^2 for k from 1 to 10"
123
+ "summation of 1/n^2 for n from 1 to infinity"
124
+
125
+ Complex numbers:
126
+ "real part of 3 + 4*I"
127
+ "modulus of 3 + 4*I"
128
+
129
+ β€’ Arithmetic β†’ computed instantly
130
+ e.g. "2 + 3 * (4 ^ 2)"
131
+
132
+ β€’ Knowledge β†’ local KB first, then LLM
133
+ e.g. "What is quantum computing?"
134
+
135
+ β€’ Chat β†’ pattern rules, then LLM
136
+ e.g. "Hello!"
137
+ """
138
+
139
+
140
+ # ─────────────────────────────────────────────────────────────────────────────
141
+ # Terminal helpers
142
+ # ─────────────────────────────────────────────────────────────────────────────
143
+
144
+ def _print(text: str, color: str = "") -> None:
145
+ print(f"{color}{text}{Style.RESET_ALL}" if color else text)
146
+
147
+
148
+ def _prompt() -> str:
149
+ try:
150
+ return input(f"\n{Fore.CYAN}You{Style.RESET_ALL} β€Ί ").strip()
151
+ except (EOFError, KeyboardInterrupt):
152
+ return "/exit"
153
+
154
+
155
+ def _respond(label: str, text: str) -> None:
156
+ print(
157
+ f"\n{Fore.GREEN}AnveshAI{Style.RESET_ALL} "
158
+ f"[{Fore.YELLOW}{label}{Style.RESET_ALL}] β€Ί {text}"
159
+ )
160
+
161
+
162
+ def _system(text: str) -> None:
163
+ print(f"{Fore.MAGENTA} {text}{Style.RESET_ALL}")
164
+
165
+
166
+ # ─────────────────────────────────────────────────────────────────────────────
167
+ # Response Composer
168
+ # ─────────────────────────────────────────────────────────────────────────────
169
+
170
+ def compose_response(
171
+ user_input: str,
172
+ intent: str,
173
+ knowledge_engine: KnowledgeEngine,
174
+ conversation_engine: ConversationEngine,
175
+ llm_engine: LLMEngine,
176
+ reasoning_engine: ReasoningEngine,
177
+ ) -> tuple[str, str]:
178
+ """
179
+ Route input through the full hierarchy with chain-of-thought reasoning.
180
+ Returns (label, response_text).
181
+
182
+ Labels:
183
+ Math – plain arithmetic result (instant)
184
+ AdvMath+CoT+LLM – SymPy computed, reasoning planned, LLM explained
185
+ AdvMath+CoT – SymPy computed, reasoning-guided LLM fallback
186
+ Knowledge – local KB answered
187
+ LLM+CoT-KB – KB miss; reasoning-guided LLM answered
188
+ Chat – conversation pattern matched
189
+ LLM+CoT – reasoning-guided LLM answered
190
+ """
191
+
192
+ # ── Simple arithmetic ─────────────────────────────────────────────────────
193
+ if intent == "math":
194
+ return "Math", math_evaluate(user_input)
195
+
196
+ # ── Advanced math ─────────────────────────────────────────────────────────
197
+ if intent == "advanced_math":
198
+ success, result_str, _latex = advanced_math_solve(user_input)
199
+
200
+ if success:
201
+ _system(f"SymPy β†’ {result_str}")
202
+ _system("Reasoning engine: decomposing problem…")
203
+ plan = reasoning_engine.analyze(
204
+ user_input, intent, has_symbolic_result=True
205
+ )
206
+ _system(plan.summary())
207
+ if plan.warnings:
208
+ for w in plan.warnings:
209
+ _system(f" ⚠ {w}")
210
+ _system("Building chain-of-thought prompt β†’ LLM…")
211
+ prompt = reasoning_engine.build_math_prompt(user_input, result_str, plan)
212
+ explanation = llm_engine.generate(
213
+ prompt,
214
+ system_prompt=MATH_SYSTEM_PROMPT,
215
+ temperature=MATH_TEMPERATURE,
216
+ )
217
+ full_response = (
218
+ f"{result_str}\n\n"
219
+ f"[Reasoning: {plan.problem_type} | {plan.strategy[:60]}]\n\n"
220
+ f"{explanation}"
221
+ )
222
+ return "AdvMath+CoT+LLM", full_response
223
+
224
+ else:
225
+ _system(f"SymPy error: {result_str}")
226
+ _system("Reasoning engine: building fallback chain-of-thought…")
227
+ plan = reasoning_engine.analyze(user_input, intent)
228
+ _system(plan.summary())
229
+ prompt = reasoning_engine.build_math_fallback_prompt(
230
+ user_input, plan, error_context=result_str
231
+ )
232
+ llm_response = llm_engine.generate(prompt)
233
+ return "AdvMath+CoT", llm_response
234
+
235
+ # ── Knowledge ─────────────────────────────��───────────────────────────────
236
+ if intent == "knowledge":
237
+ kb_response, kb_found = knowledge_engine.query(user_input)
238
+ if kb_found:
239
+ return "Knowledge", kb_response
240
+
241
+ _system("KB: no match β€” reasoning engine + LLM…")
242
+ plan = reasoning_engine.analyze(user_input, intent)
243
+ _system(plan.summary())
244
+ prompt = reasoning_engine.build_general_prompt(
245
+ user_input, intent, kb_response, plan
246
+ )
247
+ return "LLM+CoT-KB", llm_engine.generate(prompt)
248
+
249
+ # ── Conversation ──────────────────────────────────────────────────────────
250
+ chat_response, pattern_matched = conversation_engine.respond(user_input)
251
+ if pattern_matched:
252
+ return "Chat", chat_response
253
+
254
+ _system("No pattern match β€” reasoning engine + LLM…")
255
+ plan = reasoning_engine.analyze(user_input, intent)
256
+ _system(plan.summary())
257
+ prompt = reasoning_engine.build_general_prompt(user_input, intent, "", plan)
258
+ return "LLM+CoT", llm_engine.generate(prompt)
259
+
260
+
261
+ # ─────────────────────────────────────────────────────────────────────────────
262
+ # Main loop
263
+ # ─────────────────────────────────────────────────────────────────────────────
264
+
265
+ def main() -> None:
266
+ _print(BANNER, Fore.CYAN)
267
+ _system("Initialising modules…")
268
+
269
+ initialize_db()
270
+ _system("βœ” Memory (SQLite) ready")
271
+
272
+ knowledge_engine = KnowledgeEngine()
273
+ _system("βœ” Knowledge base loaded" if knowledge_engine.is_loaded() else "⚠ knowledge.txt not found")
274
+
275
+ conversation_engine = ConversationEngine()
276
+ _system("βœ” Conversation engine ready")
277
+ _system("βœ” Math engine ready (AST safe-eval)")
278
+ _system("βœ” Advanced math engine ready (SymPy β€” 31+ operations)")
279
+ _system("βœ” Reasoning engine ready (chain-of-thought + CoT planning)")
280
+ _system("βœ” Intent router ready")
281
+
282
+ llm_engine = LLMEngine()
283
+ reasoning_eng = ReasoningEngine()
284
+ _system("βœ” LLM engine ready (Qwen2.5-0.5B loads on first use)")
285
+
286
+ _print(f"\n{Fore.WHITE}Type /help for commands or just start chatting!{Style.RESET_ALL}")
287
+
288
+ while True:
289
+ user_input = _prompt()
290
+ if not user_input:
291
+ continue
292
+
293
+ intent = classify_intent(user_input)
294
+
295
+ # ── System commands ───────────────────────────────────────────────────
296
+ if intent == "system":
297
+ cmd = user_input.lower().split()[0]
298
+ if cmd == "/exit":
299
+ _print(f"\n{Fore.CYAN}Goodbye! Session closed.{Style.RESET_ALL}")
300
+ sys.exit(0)
301
+ elif cmd == "/history":
302
+ _print(f"\n{Fore.YELLOW}── Conversation History ─────────────────────{Style.RESET_ALL}")
303
+ _print(format_history())
304
+ _print(f"{Fore.YELLOW}─────────────────────────────────────────────{Style.RESET_ALL}")
305
+ elif cmd == "/help":
306
+ _print(f"\n{Fore.YELLOW}── Help ──────────────────────────────────────{Style.RESET_ALL}")
307
+ _print(HELP_TEXT)
308
+ _print(f"{Fore.YELLOW}─────────────────────────────────────────────{Style.RESET_ALL}")
309
+ else:
310
+ _respond("System", f"Unknown command '{user_input}'. Type /help.")
311
+ continue
312
+
313
+ # ── Compose response ──────────────────────────────────────────────────
314
+ label, response = compose_response(
315
+ user_input, intent, knowledge_engine,
316
+ conversation_engine, llm_engine, reasoning_eng
317
+ )
318
+ _respond(label, response)
319
+ save_interaction(user_input, response)
320
+
321
+
322
+ if __name__ == "__main__":
323
+ main()
math_engine.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Math Engine β€” safely evaluates mathematical expressions.
3
+
4
+ Supported operations:
5
+ + Addition
6
+ - Subtraction
7
+ * Multiplication
8
+ / Division
9
+ % Modulo
10
+ ^ Exponentiation (converted to **)
11
+ () Parentheses for grouping
12
+
13
+ Uses a whitelist-based safe evaluator to avoid arbitrary code execution.
14
+ """
15
+
16
+ import re
17
+ import ast
18
+ import operator
19
+
20
+
21
+ # Allowed AST node types for safe evaluation
22
+ ALLOWED_NODES = (
23
+ ast.Expression,
24
+ ast.BinOp,
25
+ ast.UnaryOp,
26
+ ast.Constant,
27
+ ast.Add,
28
+ ast.Sub,
29
+ ast.Mult,
30
+ ast.Div,
31
+ ast.Mod,
32
+ ast.Pow,
33
+ ast.USub, # unary minus, e.g. -5
34
+ ast.UAdd, # unary plus, e.g. +5
35
+ )
36
+
37
+ # Mapping AST operators to Python functions
38
+ OPERATORS = {
39
+ ast.Add: operator.add,
40
+ ast.Sub: operator.sub,
41
+ ast.Mult: operator.mul,
42
+ ast.Div: operator.truediv,
43
+ ast.Mod: operator.mod,
44
+ ast.Pow: operator.pow,
45
+ ast.USub: operator.neg,
46
+ ast.UAdd: operator.pos,
47
+ }
48
+
49
+ # Prefixes the user might add before the expression
50
+ STRIP_PREFIXES = (
51
+ "calculate", "compute", "evaluate", "solve",
52
+ "what is", "what's",
53
+ )
54
+
55
+
56
+ def extract_expression(user_input: str) -> str:
57
+ """
58
+ Strip any natural-language prefixes to get the raw math expression.
59
+ Also converts '^' to '**' for exponentiation.
60
+ """
61
+ text = user_input.strip()
62
+ lowered = text.lower()
63
+
64
+ for prefix in STRIP_PREFIXES:
65
+ if lowered.startswith(prefix):
66
+ text = text[len(prefix):].strip()
67
+ break
68
+
69
+ # Replace caret with Python exponentiation operator
70
+ text = text.replace("^", "**")
71
+
72
+ return text
73
+
74
+
75
+ def safe_eval(expression: str) -> float:
76
+ """
77
+ Safely evaluate a mathematical expression string.
78
+
79
+ Raises ValueError if the expression is invalid or contains disallowed
80
+ operations.
81
+ """
82
+ try:
83
+ tree = ast.parse(expression, mode="eval")
84
+ except SyntaxError:
85
+ raise ValueError(f"Invalid mathematical expression: '{expression}'")
86
+
87
+ # Validate every node in the AST
88
+ for node in ast.walk(tree):
89
+ if not isinstance(node, ALLOWED_NODES):
90
+ raise ValueError(
91
+ f"Unsafe or unsupported operation detected: {type(node).__name__}"
92
+ )
93
+
94
+ return _eval_node(tree.body)
95
+
96
+
97
+ def _eval_node(node: ast.AST) -> float:
98
+ """Recursively evaluate an AST node."""
99
+ if isinstance(node, ast.Constant):
100
+ if isinstance(node.value, (int, float)):
101
+ return node.value
102
+ raise ValueError(f"Unsupported constant type: {type(node.value)}")
103
+
104
+ if isinstance(node, ast.BinOp):
105
+ left = _eval_node(node.left)
106
+ right = _eval_node(node.right)
107
+ op_func = OPERATORS.get(type(node.op))
108
+ if op_func is None:
109
+ raise ValueError(f"Unsupported binary operator: {type(node.op).__name__}")
110
+ if isinstance(node.op, ast.Div) and right == 0:
111
+ raise ValueError("Division by zero is not allowed.")
112
+ return op_func(left, right)
113
+
114
+ if isinstance(node, ast.UnaryOp):
115
+ operand = _eval_node(node.operand)
116
+ op_func = OPERATORS.get(type(node.op))
117
+ if op_func is None:
118
+ raise ValueError(f"Unsupported unary operator: {type(node.op).__name__}")
119
+ return op_func(operand)
120
+
121
+ raise ValueError(f"Unsupported AST node: {type(node).__name__}")
122
+
123
+
124
+ def evaluate(user_input: str) -> str:
125
+ """
126
+ Main entry point: extract the expression, evaluate it, and return
127
+ a formatted result string.
128
+ """
129
+ expression = extract_expression(user_input)
130
+
131
+ if not expression:
132
+ return "Please provide a mathematical expression to evaluate."
133
+
134
+ try:
135
+ result = safe_eval(expression)
136
+
137
+ # Format: show integer if result is whole, else show decimal
138
+ if isinstance(result, float) and result.is_integer():
139
+ formatted = str(int(result))
140
+ else:
141
+ formatted = f"{result:.6g}" # up to 6 significant figures
142
+
143
+ return f"The result of {expression} = {formatted}"
144
+
145
+ except ValueError as e:
146
+ return f"Math error: {e}"
147
+ except Exception as e:
148
+ return f"Unexpected error evaluating expression: {e}"
memory.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Memory System β€” stores and retrieves conversation history using SQLite.
3
+
4
+ Schema:
5
+ conversations (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ timestamp TEXT NOT NULL, -- ISO 8601 format
8
+ user_input TEXT NOT NULL,
9
+ response TEXT NOT NULL
10
+ )
11
+
12
+ Commands:
13
+ /history β†’ display the last 10 interactions
14
+ """
15
+
16
+ import sqlite3
17
+ import os
18
+ from datetime import datetime
19
+ from typing import List, Tuple
20
+
21
+
22
+ DB_PATH = os.path.join(os.path.dirname(__file__), "anveshai_memory.db")
23
+ HISTORY_LIMIT = 10
24
+
25
+
26
+ def _get_connection() -> sqlite3.Connection:
27
+ """Open and return a SQLite database connection."""
28
+ conn = sqlite3.connect(DB_PATH)
29
+ conn.row_factory = sqlite3.Row
30
+ return conn
31
+
32
+
33
+ def initialize_db() -> None:
34
+ """
35
+ Create the conversations table if it does not already exist.
36
+ Called once at startup.
37
+ """
38
+ with _get_connection() as conn:
39
+ conn.execute("""
40
+ CREATE TABLE IF NOT EXISTS conversations (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ timestamp TEXT NOT NULL,
43
+ user_input TEXT NOT NULL,
44
+ response TEXT NOT NULL
45
+ )
46
+ """)
47
+ conn.commit()
48
+
49
+
50
+ def save_interaction(user_input: str, response: str) -> None:
51
+ """
52
+ Persist a single interaction (user input + assistant response) to the DB.
53
+ """
54
+ timestamp = datetime.now().isoformat(sep=" ", timespec="seconds")
55
+ with _get_connection() as conn:
56
+ conn.execute(
57
+ "INSERT INTO conversations (timestamp, user_input, response) VALUES (?, ?, ?)",
58
+ (timestamp, user_input, response),
59
+ )
60
+ conn.commit()
61
+
62
+
63
+ def get_recent_history(limit: int = HISTORY_LIMIT) -> List[sqlite3.Row]:
64
+ """
65
+ Retrieve the most recent `limit` interactions, ordered oldest-first.
66
+ """
67
+ with _get_connection() as conn:
68
+ cursor = conn.execute(
69
+ """
70
+ SELECT id, timestamp, user_input, response
71
+ FROM conversations
72
+ ORDER BY id DESC
73
+ LIMIT ?
74
+ """,
75
+ (limit,),
76
+ )
77
+ rows = cursor.fetchall()
78
+ # Reverse so oldest appears first
79
+ return list(reversed(rows))
80
+
81
+
82
+ def format_history() -> str:
83
+ """
84
+ Return a formatted string of the last HISTORY_LIMIT interactions.
85
+ Returns a notice if there's nothing to show.
86
+ """
87
+ rows = get_recent_history()
88
+
89
+ if not rows:
90
+ return "No conversation history yet."
91
+
92
+ lines = [f" Last {len(rows)} interaction(s):\n"]
93
+ for row in rows:
94
+ lines.append(f" [{row['timestamp']}]")
95
+ lines.append(f" You : {row['user_input']}")
96
+ lines.append(f" AnveshAI : {row['response']}")
97
+ lines.append("") # blank line between entries
98
+
99
+ return "\n".join(lines).rstrip()
reasoning_engine.py ADDED
@@ -0,0 +1,599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced Reasoning Engine β€” Chain-of-Thought (CoT) reasoning layer.
3
+
4
+ Sits between the intent router and the LLM / specialist engines.
5
+ Provides structured, multi-step reasoning for every response:
6
+
7
+ Stage 1 β€” Problem Analysis
8
+ Β· Identify problem type, domain, and sub-questions
9
+ Β· Detect ambiguity or missing information
10
+ Β· Choose the optimal resolution strategy
11
+
12
+ Stage 2 β€” Chain-of-Thought Planning
13
+ Β· Decompose complex problems into ordered sub-steps
14
+ Β· Identify dependencies between sub-steps
15
+ Β· Estimate difficulty and required knowledge
16
+
17
+ Stage 3 β€” Verification & Confidence
18
+ Β· Cross-check reasoning against known constraints
19
+ Β· Assign confidence level (HIGH / MEDIUM / LOW)
20
+ Β· Flag any assumptions made
21
+
22
+ Stage 4 β€” Response Synthesis
23
+ Β· Compose final LLM prompt that enforces the reasoning trace
24
+ Β· Force the LLM to follow the plan and not deviate
25
+
26
+ For advanced math the engine adds a symbolic pre-analysis step:
27
+ Β· Identify operation type and variable structure
28
+ Β· Reason about the expected form of the answer
29
+ Β· Verify the SymPy result makes mathematical sense
30
+
31
+ Usage:
32
+ from reasoning_engine import ReasoningEngine
33
+ re = ReasoningEngine()
34
+ plan = re.analyze("integrate x^2 sin(x)", intent="advanced_math")
35
+ prompt = re.build_math_prompt(user_input, sympy_result, plan)
36
+ prompt_gen = re.build_general_prompt(user_input, intent, context, plan)
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import re
42
+ from dataclasses import dataclass, field
43
+ from typing import Optional
44
+
45
+
46
+ # ─────────────────────────────────────────────────────────────────────────────
47
+ # Data structures
48
+ # ─────────────────────────────────────────────────────────────────────────────
49
+
50
+ @dataclass
51
+ class ReasoningPlan:
52
+ """Structured reasoning plan produced by the engine."""
53
+ problem_type: str = "unknown"
54
+ domain: str = "general"
55
+ sub_problems: list[str] = field(default_factory=list)
56
+ strategy: str = ""
57
+ expected_form: str = ""
58
+ assumptions: list[str] = field(default_factory=list)
59
+ confidence: str = "MEDIUM" # HIGH / MEDIUM / LOW
60
+ reasoning_steps: list[str] = field(default_factory=list)
61
+ warnings: list[str] = field(default_factory=list)
62
+
63
+ def summary(self) -> str:
64
+ """One-line summary for console display."""
65
+ return (
66
+ f"[Reasoning] domain={self.domain} | strategy={self.strategy[:60]} "
67
+ f"| confidence={self.confidence}"
68
+ )
69
+
70
+ def full_trace(self) -> str:
71
+ """Full reasoning trace as a numbered list."""
72
+ lines = [f"Problem type: {self.problem_type}", f"Domain: {self.domain}"]
73
+ if self.sub_problems:
74
+ lines.append("Sub-problems:")
75
+ for i, sp in enumerate(self.sub_problems, 1):
76
+ lines.append(f" {i}. {sp}")
77
+ lines.append(f"Strategy: {self.strategy}")
78
+ if self.expected_form:
79
+ lines.append(f"Expected answer form: {self.expected_form}")
80
+ if self.assumptions:
81
+ lines.append("Assumptions: " + "; ".join(self.assumptions))
82
+ if self.warnings:
83
+ lines.append("Warnings: " + "; ".join(self.warnings))
84
+ lines.append(f"Confidence: {self.confidence}")
85
+ return "\n".join(lines)
86
+
87
+
88
+ # ─────────────────────────────────────────────────────────────────────────────
89
+ # Domain / problem-type classifiers
90
+ # ─────────────────────────────────────────────────────────────────────────────
91
+
92
+ _DOMAIN_HINTS: dict[str, list[str]] = {
93
+ "calculus": [
94
+ "integrate", "integral", "derivative", "differentiate",
95
+ "d/dx", "limit", "lim", "antiderivative",
96
+ ],
97
+ "algebra": [
98
+ "solve", "factor", "expand", "simplify", "roots", "zeros",
99
+ "equation", "polynomial", "quadratic",
100
+ ],
101
+ "linear_algebra": [
102
+ "matrix", "eigenvalue", "eigenvector", "determinant",
103
+ "inverse", "rank", "trace", "vector", "dot product", "cross product",
104
+ ],
105
+ "differential_equations": [
106
+ "differential equation", "ode", "dy/dx", "y''", "y'", "dsolve",
107
+ ],
108
+ "transforms": [
109
+ "laplace", "fourier", "z-transform", "inverse laplace",
110
+ ],
111
+ "series": [
112
+ "taylor", "maclaurin", "power series", "series expansion",
113
+ ],
114
+ "number_theory": [
115
+ "prime", "gcd", "lcm", "modular", "mod ", "divisible",
116
+ "factorization", "congruence",
117
+ ],
118
+ "statistics": [
119
+ "mean", "median", "mode", "variance", "standard deviation",
120
+ "average", "probability", "distribution",
121
+ ],
122
+ "combinatorics": [
123
+ "factorial", "binomial", "permutation", "combination", "choose",
124
+ "nCr", "nPr",
125
+ ],
126
+ "complex_analysis": [
127
+ "complex", "imaginary", "real part", "modulus", "argument",
128
+ "conjugate", "polar form",
129
+ ],
130
+ "physics": [
131
+ "velocity", "acceleration", "force", "energy", "momentum",
132
+ "electric", "magnetic", "quantum", "wave", "frequency",
133
+ ],
134
+ "computer_science": [
135
+ "algorithm", "complexity", "big o", "sorting", "graph",
136
+ "recursion", "dynamic programming",
137
+ ],
138
+ }
139
+
140
+ _PROBLEM_TYPE_PATTERNS: list[tuple[str, str]] = [
141
+ # Highest-priority: detect before generic "solve" or "equation"
142
+ (r'\bdifferential\s+equation\b|\bode\b|\bdsolve\b', "ode_solving"),
143
+ (r'\bintegrate\b|\bintegral\b|\bantiderivative\b', "integration"),
144
+ (r'\bderivative\b|\bdifferentiate\b|\bd/d[a-z]\b', "differentiation"),
145
+ (r'\blimit\b|\blim\s', "limit_evaluation"),
146
+ (r'\beigenvalue\b|\beigenvector\b', "matrix_operation"),
147
+ (r'\bdeterminant\b|\bdet\b|\binverse\s+matrix\b|\bmatrix\s+rank\b|\bmatrix\s+trace\b', "matrix_operation"),
148
+ (r'\btaylor\b|\bmaclaurin\b|\bseries\s+expansion\b|\bpower\s+series\b', "series_expansion"),
149
+ (r'\blaplace\s+transform\b|\blaplace\s+of\b', "laplace_transform"),
150
+ (r'\bfourier\s+transform\b|\bfourier\s+of\b', "fourier_transform"),
151
+ (r'\bprime\s+factor|\bgcd\b|\blcm\b|\bmod\b|\bmodular\b', "number_theory"),
152
+ (r'\bfactorial\b|\bbinomial\s+coeff|\bpermutation\b|\bnCr\b|\bnPr\b', "combinatorics"),
153
+ (r'\bsum\s+of\b|\bsummation\b|\bsum\b.*\bfrom\b', "summation"),
154
+ (r'\bmean\b|\bmedian\b|\bvariance\b|\bstandard\s+deviation\b|\baverage\b', "statistical_analysis"),
155
+ (r'\bcomplex\s+number\b|\bimaginary\s+part\b|\breal\s+part\b|\bmodulus\s+of\b|\bargument\s+of\b', "complex_number"),
156
+ (r'\bfactor\b|\bfactorise\b|\bfactorize\b', "factorization"),
157
+ (r'\bexpand\b', "algebraic_expansion"),
158
+ (r'\bsimplify\b', "simplification"),
159
+ (r'\bsolve\b|\broots?\s+of\b|\bzeros?\s+of\b', "equation_solving"),
160
+ (r'\bwhat\s+is\b|\bwho\s+is\b|\bexplain\b|\bdefine\b', "knowledge_retrieval"),
161
+ (r'\bhow\s+to\b|\bhow\s+does\b', "procedural_knowledge"),
162
+ (r'\bwhy\b', "explanatory_reasoning"),
163
+ (r'\bcompare\b|\bdifference\s+between\b', "comparative_analysis"),
164
+ ]
165
+
166
+ _STRATEGIES: dict[str, str] = {
167
+ "integration": "Apply integration rules (u-sub, IBP, trig-sub, or direct antiderivative)",
168
+ "differentiation": "Apply chain rule, product rule, quotient rule, or basic derivative rules",
169
+ "limit_evaluation": "Apply L'HΓ΄pital's rule, algebraic manipulation, or squeeze theorem",
170
+ "equation_solving": "Factor, use quadratic formula, or numerical methods as needed",
171
+ "factorization": "Factor out GCF, then use special patterns (difference of squares, sum/difference of cubes)",
172
+ "algebraic_expansion":"Apply binomial theorem or FOIL method for products",
173
+ "simplification": "Cancel common factors, apply trig/logarithm identities, or algebraic reduction",
174
+ "matrix_operation": "Apply matrix algorithms: cofactor expansion (det), row reduction (rank/inverse), characteristic polynomial (eigenvalues)",
175
+ "ode_solving": "Classify ODE (linear/separable/exact/homogeneous) and apply corresponding solution method",
176
+ "series_expansion": "Compute Taylor/Maclaurin coefficients using repeated differentiation",
177
+ "laplace_transform": "Apply Laplace transform table entries and linearity",
178
+ "fourier_transform": "Apply Fourier transform definition and standard pairs",
179
+ "number_theory": "Apply Euclidean algorithm (GCD/LCM) or prime factorization via trial division",
180
+ "statistical_analysis": "Compute descriptive statistics: mean, variance (E[(X-ΞΌ)Β²]), std deviation",
181
+ "combinatorics": "Apply counting principles: factorial, binomial theorem, or permutation formulas",
182
+ "complex_number": "Use Cartesian/polar form of complex numbers and their properties",
183
+ "knowledge_retrieval": "Retrieve and synthesize relevant factual information",
184
+ "procedural_knowledge": "Provide a clear step-by-step explanation of the procedure",
185
+ "explanatory_reasoning": "Reason about causes, mechanisms, or justifications",
186
+ "comparative_analysis": "Identify key dimensions of comparison and contrast each one",
187
+ "unknown": "Analyze the problem and apply the most relevant reasoning approach",
188
+ }
189
+
190
+ _EXPECTED_FORMS: dict[str, str] = {
191
+ "integration": "A symbolic function + constant of integration C (indefinite) or a real number (definite)",
192
+ "differentiation": "A symbolic expression of the same or lower degree",
193
+ "limit_evaluation": "A real number, ∞, -∞, or 'does not exist'",
194
+ "equation_solving": "One or more numerical or symbolic values for the unknown",
195
+ "factorization": "A product of irreducible factors",
196
+ "series_expansion": "A polynomial in x up to the given order, plus O(x^n) remainder",
197
+ "matrix_operation": "A scalar (det/rank/trace) or matrix (inverse/eigenvectors)",
198
+ "ode_solving": "A function y(x) with integration constants C1, C2, …",
199
+ "laplace_transform": "A rational or transcendental function of s",
200
+ "fourier_transform": "A function of the frequency variable k",
201
+ "statistical_analysis": "Real-valued descriptive statistics (mean, variance, std)",
202
+ "combinatorics": "A non-negative integer",
203
+ }
204
+
205
+
206
+ # ─────────────────────────────────────────────────────────────────────────────
207
+ # Multi-hop decomposer
208
+ # ─────────────────────────────────────────────────────────────────────────────
209
+
210
+ def _decompose(user_input: str, problem_type: str, intent: str) -> list[str]:
211
+ """Break the problem into an ordered list of sub-problems / reasoning steps."""
212
+ lowered = user_input.lower()
213
+
214
+ # Math decompositions
215
+ if problem_type == "integration":
216
+ steps = ["Identify the integrand and the variable of integration"]
217
+ if "from" in lowered and "to" in lowered:
218
+ steps.append("Confirm the integration limits (lower and upper bounds)")
219
+ steps += [
220
+ "Check whether u-substitution, integration by parts, or a standard form applies",
221
+ "Compute the antiderivative step by step",
222
+ "Apply the Fundamental Theorem of Calculus if limits are given",
223
+ "Simplify the result and add C for indefinite integrals",
224
+ ]
225
+ return steps
226
+
227
+ if problem_type == "differentiation":
228
+ steps = ["Identify the function and the differentiation variable"]
229
+ if "second" in lowered or "third" in lowered or "2nd" in lowered or "3rd" in lowered:
230
+ steps.append("Determine the required order of differentiation")
231
+ steps += [
232
+ "Identify which rules apply (chain rule, product rule, quotient rule)",
233
+ "Differentiate term by term",
234
+ "Simplify the derivative expression",
235
+ ]
236
+ return steps
237
+
238
+ if problem_type == "limit_evaluation":
239
+ return [
240
+ "Identify the function and the point of approach",
241
+ "Check for direct substitution; if it gives 0/0 or ∞/∞, apply L'Hôpital's rule",
242
+ "Alternatively, try algebraic simplification or known limit results",
243
+ "Evaluate the limit or determine if it diverges",
244
+ ]
245
+
246
+ if problem_type == "equation_solving":
247
+ steps = ["Identify the type of equation (linear, quadratic, polynomial, transcendental)"]
248
+ if "=" in user_input:
249
+ steps.append("Rearrange so one side is 0 (or use direct substitution for LHS=RHS)")
250
+ steps += [
251
+ "Choose the solution method: factoring, quadratic formula, or numeric methods",
252
+ "Find all solutions in the required domain",
253
+ "Verify each solution satisfies the original equation",
254
+ ]
255
+ return steps
256
+
257
+ if problem_type == "ode_solving":
258
+ return [
259
+ "Classify the ODE: order, linearity, and special form",
260
+ "For 1st order: check separable, linear (integrating factor), exact",
261
+ "For 2nd order: find the characteristic equation and its roots",
262
+ "Write the general solution with arbitrary constants C1, C2, …",
263
+ "Apply initial conditions if provided to find particular solution",
264
+ ]
265
+
266
+ if problem_type == "matrix_operation":
267
+ steps = ["Identify the matrix dimensions and operation required"]
268
+ if "eigenvalue" in lowered:
269
+ steps += [
270
+ "Form the characteristic polynomial det(A - Ξ»I) = 0",
271
+ "Solve for eigenvalues Ξ»",
272
+ "For each eigenvalue, solve (A - Ξ»I)v = 0 for eigenvectors",
273
+ ]
274
+ elif "determinant" in lowered or "det" in lowered:
275
+ steps += [
276
+ "Choose expansion method: cofactor expansion or row reduction",
277
+ "Compute the determinant",
278
+ ]
279
+ elif "inverse" in lowered:
280
+ steps += [
281
+ "Augment the matrix with the identity: [A | I]",
282
+ "Row-reduce to obtain [I | A⁻¹]",
283
+ ]
284
+ return steps
285
+
286
+ if problem_type == "series_expansion":
287
+ return [
288
+ "Identify the function and expansion point",
289
+ "Compute successive derivatives at the expansion point",
290
+ "Form the Taylor/Maclaurin series coefficients: f^(n)(a) / n!",
291
+ "Write the series up to the required order",
292
+ "Identify the pattern or general term if possible",
293
+ ]
294
+
295
+ if problem_type == "laplace_transform":
296
+ return [
297
+ "Express the function in terms of known Laplace pairs",
298
+ "Apply linearity of the Laplace transform",
299
+ "Use standard table entries or direct computation",
300
+ "Simplify the result in the s-domain",
301
+ ]
302
+
303
+ # Knowledge / reasoning decompositions
304
+ if problem_type == "knowledge_retrieval":
305
+ return [
306
+ "Identify the core concept being asked about",
307
+ "Recall the definition and key properties",
308
+ "Provide relevant examples or applications",
309
+ "Connect to related concepts if helpful",
310
+ ]
311
+
312
+ if problem_type == "explanatory_reasoning":
313
+ return [
314
+ "Identify the phenomenon / concept requiring explanation",
315
+ "Determine the causal chain or mechanism",
316
+ "State any assumptions or simplifications",
317
+ "Synthesise a clear, evidence-based explanation",
318
+ ]
319
+
320
+ if problem_type == "comparative_analysis":
321
+ return [
322
+ "Identify what is being compared",
323
+ "List key dimensions of comparison",
324
+ "Analyse each dimension for both subjects",
325
+ "Summarise similarities and differences",
326
+ ]
327
+
328
+ # Default: general reasoning
329
+ return [
330
+ "Understand the question and identify the core task",
331
+ "Break the problem into smaller sub-problems",
332
+ "Address each sub-problem in order",
333
+ "Synthesise a complete and accurate answer",
334
+ ]
335
+
336
+
337
+ # ─────────────────────────────────────────────────────────────────────────────
338
+ # Confidence estimator
339
+ # ─────────────────────────────────────────────────────────────────────────────
340
+
341
+ def _estimate_confidence(
342
+ user_input: str,
343
+ problem_type: str,
344
+ intent: str,
345
+ has_symbolic_result: bool = False,
346
+ ) -> str:
347
+ """Heuristically estimate confidence in the system's ability to answer."""
348
+ if has_symbolic_result:
349
+ return "HIGH" # SymPy computed it β€” we're certain
350
+
351
+ lowered = user_input.lower()
352
+
353
+ # High confidence for well-defined math types
354
+ high_confidence_types = {
355
+ "integration", "differentiation", "limit_evaluation",
356
+ "equation_solving", "factorization", "algebraic_expansion",
357
+ "simplification", "series_expansion", "number_theory",
358
+ "statistical_analysis", "combinatorics",
359
+ }
360
+ if problem_type in high_confidence_types:
361
+ return "HIGH"
362
+
363
+ # Medium confidence for conceptual / knowledge
364
+ if intent in ("knowledge", "conversation"):
365
+ # Simple factual questions tend to be higher confidence
366
+ if any(kw in lowered for kw in ["what is", "define", "who is"]):
367
+ return "MEDIUM"
368
+ # Open-ended or opinion questions are lower
369
+ if any(kw in lowered for kw in ["why", "opinion", "best", "should i"]):
370
+ return "LOW"
371
+ return "MEDIUM"
372
+
373
+ return "MEDIUM"
374
+
375
+
376
+ # ─────────────────────────────────────────────────────────────────────────────
377
+ # Warnings detector
378
+ # ─────────────────────────────────────────────────────────────────────────────
379
+
380
+ def _detect_warnings(user_input: str, problem_type: str) -> list[str]:
381
+ """Flag potential issues in the problem statement."""
382
+ warnings = []
383
+ lowered = user_input.lower()
384
+
385
+ if len(user_input.strip()) < 5:
386
+ warnings.append("Input is very short β€” may be ambiguous")
387
+
388
+ if problem_type == "equation_solving" and "=" not in user_input and "solve" in lowered:
389
+ warnings.append("No '=' found β€” treating expression as equal to 0")
390
+
391
+ if problem_type == "integration" and "from" in lowered and "to" not in lowered:
392
+ warnings.append("'from' found but 'to' is missing β€” treating as indefinite integral")
393
+
394
+ if problem_type in ("differentiation", "integration") and not any(
395
+ c in user_input for c in list("xyztnkabcmnpqrs")
396
+ ):
397
+ warnings.append("No variable detected β€” defaulting to x")
398
+
399
+ if "undefined" in lowered or "infinity" in lowered:
400
+ warnings.append("Expression may involve singularities or unbounded behaviour")
401
+
402
+ return warnings
403
+
404
+
405
+ # ────────────────────────────────────────────────────────────────���────────────
406
+ # Public interface
407
+ # ─────────────────────────────────────────────────────────────────────────────
408
+
409
+ class ReasoningEngine:
410
+ """
411
+ Chain-of-Thought reasoning engine.
412
+
413
+ Usage:
414
+ re = ReasoningEngine()
415
+ plan = re.analyze(user_input, intent)
416
+ prompt = re.build_math_prompt(user_input, sympy_result, plan)
417
+ prompt = re.build_general_prompt(user_input, intent, context, plan)
418
+ """
419
+
420
+ def analyze(
421
+ self,
422
+ user_input: str,
423
+ intent: str,
424
+ has_symbolic_result: bool = False,
425
+ ) -> ReasoningPlan:
426
+ """
427
+ Produce a structured ReasoningPlan for the given input.
428
+
429
+ Args:
430
+ user_input: Raw user query.
431
+ intent: Classified intent (advanced_math / math / knowledge / conversation).
432
+ has_symbolic_result: True when SymPy has already computed the answer.
433
+
434
+ Returns:
435
+ ReasoningPlan with problem type, strategy, sub-problems, confidence.
436
+ """
437
+ lowered = user_input.lower()
438
+
439
+ # 1. Detect domain
440
+ domain = "general"
441
+ for d, keywords in _DOMAIN_HINTS.items():
442
+ for kw in keywords:
443
+ if kw in lowered:
444
+ domain = d
445
+ break
446
+ if domain != "general":
447
+ break
448
+
449
+ # 2. Classify problem type (list preserves priority order)
450
+ problem_type = "unknown"
451
+ for pattern, ptype in _PROBLEM_TYPE_PATTERNS:
452
+ if re.search(pattern, lowered):
453
+ problem_type = ptype
454
+ break
455
+
456
+ # 3. Strategy & expected answer form
457
+ strategy = _STRATEGIES.get(problem_type, _STRATEGIES["unknown"])
458
+ expected_form = _EXPECTED_FORMS.get(problem_type, "")
459
+
460
+ # 4. Decompose into sub-problems
461
+ sub_problems = _decompose(user_input, problem_type, intent)
462
+
463
+ # 5. Detect assumptions
464
+ assumptions = []
465
+ if domain in ("calculus", "algebra", "differential_equations"):
466
+ if not any(kw in lowered for kw in ["complex", "imaginary", "i^2"]):
467
+ assumptions.append("Working over the real numbers unless otherwise stated")
468
+ if problem_type in ("integration", "differentiation"):
469
+ assumptions.append("Function is sufficiently smooth (differentiable/integrable)")
470
+
471
+ # 6. Warnings
472
+ warnings = _detect_warnings(user_input, problem_type)
473
+
474
+ # 7. Confidence
475
+ confidence = _estimate_confidence(
476
+ user_input, problem_type, intent, has_symbolic_result
477
+ )
478
+
479
+ return ReasoningPlan(
480
+ problem_type=problem_type,
481
+ domain=domain,
482
+ sub_problems=sub_problems,
483
+ strategy=strategy,
484
+ expected_form=expected_form,
485
+ assumptions=assumptions,
486
+ confidence=confidence,
487
+ reasoning_steps=sub_problems,
488
+ warnings=warnings,
489
+ )
490
+
491
+ def build_math_prompt(
492
+ self,
493
+ user_input: str,
494
+ sympy_result: str,
495
+ plan: ReasoningPlan,
496
+ ) -> str:
497
+ """
498
+ Build an LLM prompt for advanced math where SymPy has the correct answer.
499
+ The prompt embeds the full reasoning plan so the LLM follows the exact strategy.
500
+ """
501
+ steps_str = "\n".join(
502
+ f" Step {i}: {s}" for i, s in enumerate(plan.sub_problems, 1)
503
+ )
504
+ assumptions_str = (
505
+ ("\nAssumptions: " + "; ".join(plan.assumptions))
506
+ if plan.assumptions else ""
507
+ )
508
+ expected_str = (
509
+ f"\nExpected answer form: {plan.expected_form}"
510
+ if plan.expected_form else ""
511
+ )
512
+
513
+ return (
514
+ f"PROBLEM: {user_input}\n\n"
515
+ f"VERIFIED ANSWER (computed by SymPy β€” 100% correct): {sympy_result}\n\n"
516
+ "=== YOUR TASK ===\n"
517
+ f"Explain, step by step, HOW a student arrives at: {sympy_result}\n"
518
+ "Do NOT recompute the answer. Do NOT give a different answer. "
519
+ "Your explanation must lead to exactly the verified answer above.\n\n"
520
+ f"TECHNIQUE: {plan.strategy}\n"
521
+ f"DOMAIN: {plan.domain}"
522
+ f"{expected_str}"
523
+ f"{assumptions_str}\n\n"
524
+ f"STEPS TO WALK THROUGH:\n{steps_str}\n\n"
525
+ "Write a numbered explanation. For each step:\n"
526
+ " - State what you are doing and why.\n"
527
+ " - Show the algebra or calculation clearly.\n"
528
+ " - Connect it to the next step.\n\n"
529
+ f"End with: 'Final answer: {sympy_result}' β€” use this exact wording."
530
+ )
531
+
532
+ def build_general_prompt(
533
+ self,
534
+ user_input: str,
535
+ intent: str,
536
+ context: str,
537
+ plan: ReasoningPlan,
538
+ ) -> str:
539
+ """
540
+ Build an LLM prompt for knowledge/conversation using chain-of-thought.
541
+ The plan is embedded so the LLM follows the structured reasoning trace.
542
+ """
543
+ steps_str = "\n".join(
544
+ f" {i}. {s}" for i, s in enumerate(plan.sub_problems, 1)
545
+ )
546
+ context_block = (
547
+ f"\nRELEVANT CONTEXT:\n{context}\n" if context else ""
548
+ )
549
+ confidence_note = (
550
+ "\nNote: confidence in this answer is LOW β€” state clearly if uncertain."
551
+ if plan.confidence == "LOW" else ""
552
+ )
553
+ warnings_block = ""
554
+ if plan.warnings:
555
+ warnings_block = "\nFLAGS: " + "; ".join(plan.warnings) + "\n"
556
+
557
+ return (
558
+ "You are AnveshAI, a helpful and honest AI assistant.\n"
559
+ f"QUESTION: {user_input}\n"
560
+ f"{context_block}"
561
+ f"{warnings_block}\n"
562
+ f"REASONING PLAN (follow this structure):\n{steps_str}\n\n"
563
+ f"ANSWER STRATEGY: {plan.strategy}\n"
564
+ f"{confidence_note}\n\n"
565
+ "Write a clear, thorough, well-structured response that follows "
566
+ "the reasoning plan above step by step. "
567
+ "Be concise but complete. State any assumptions or caveats explicitly."
568
+ )
569
+
570
+ def build_math_fallback_prompt(
571
+ self,
572
+ user_input: str,
573
+ plan: ReasoningPlan,
574
+ error_context: str = "",
575
+ ) -> str:
576
+ """
577
+ Prompt for when SymPy fails and the LLM must solve from scratch.
578
+ Uses chain-of-thought to maximise correctness.
579
+ """
580
+ steps_str = "\n".join(
581
+ f" Step {i}: {s}" for i, s in enumerate(plan.sub_problems, 1)
582
+ )
583
+ error_note = (
584
+ f"\nNote: automated computation failed ({error_context}). "
585
+ "Solve manually with extra care.\n" if error_context else ""
586
+ )
587
+
588
+ return (
589
+ "You are a mathematics expert.\n"
590
+ f"PROBLEM: {user_input}\n"
591
+ f"{error_note}\n"
592
+ f"PROBLEM TYPE: {plan.problem_type}\n"
593
+ f"STRATEGY: {plan.strategy}\n"
594
+ f"{f'EXPECTED ANSWER FORM: {plan.expected_form}' if plan.expected_form else ''}\n\n"
595
+ f"REASONING STEPS TO FOLLOW:\n{steps_str}\n\n"
596
+ "Solve the problem completely by following each step above. "
597
+ "Show all working in full β€” do not skip steps. "
598
+ "State the final answer clearly at the end."
599
+ )
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ colorama>=0.4.6
2
+ sympy>=1.12
3
+ llama-cpp-python>=0.3.0
4
+ huggingface-hub>=0.23.0
router.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Intent Router β€” classifies user input into one of five categories:
3
+ - system : /commands
4
+ - advanced_math : calculus, algebra, limits, matrices, series, etc.
5
+ - math : plain arithmetic (numbers only)
6
+ - knowledge : factual questions answered from the knowledge base
7
+ - conversation : everything else (chat, small talk, open questions)
8
+ """
9
+
10
+ import re
11
+
12
+
13
+ # ── Advanced math detection ───────────────────────────────────────────────────
14
+ # These keywords signal symbolic / advanced mathematics.
15
+ # Checked BEFORE the simple-arithmetic detector.
16
+ ADVANCED_MATH_KEYWORDS = [
17
+ # Calculus
18
+ "integrate", "integral", "antiderivative", "indefinite integral",
19
+ "definite integral", "∫",
20
+ "differentiate", "derivative", "d/dx", "d/dy", "d/dz", "d/dt",
21
+ "second derivative", "third derivative", "nth derivative", "partial derivative",
22
+ "limit of", "limit as", "lim ", "find the limit",
23
+ # Differential equations
24
+ "differential equation", "ode ", "dsolve", "solve the ode",
25
+ "y'' ", "y' ", "d2y", "d^2y",
26
+ # Algebra / equations
27
+ "solve ", "find roots", "zeros of", "find the value of x",
28
+ # Linear algebra
29
+ "eigenvalue", "eigenvector", "determinant of", "det of",
30
+ "inverse matrix", "matrix inverse", "rank of matrix", "matrix rank",
31
+ "trace of matrix", "matrix trace", "characteristic polynomial",
32
+ # Series & transforms
33
+ "taylor series", "maclaurin series", "series expansion", "power series",
34
+ "laplace transform", "laplace of", "inverse laplace",
35
+ "fourier transform", "fourier of",
36
+ # Symbolic manipulation
37
+ "simplify ", "simplify(", "factor ", "factorise", "factorize",
38
+ "expand ", "partial fraction",
39
+ # Number theory
40
+ "gcd(", "gcd of", "greatest common divisor", "highest common factor",
41
+ "lcm(", "lcm of", "least common multiple",
42
+ "prime factor", "prime factorization",
43
+ " mod ", "modulo ", "modular inverse",
44
+ # Statistics
45
+ "mean of", "average of", "median of", "variance of",
46
+ "standard deviation of", "std dev of",
47
+ # Combinatorics
48
+ "factorial of", "factorial(", "binomial coefficient",
49
+ "choose ", "nCr", "nPr", "permutation",
50
+ # Summations & products
51
+ "sum of ", "summation of", "βˆ‘", "∏",
52
+ # Complex numbers
53
+ "complex number", "real part of", "imaginary part of",
54
+ "modulus of", "argument of", "conjugate of",
55
+ # Trigonometric identity simplification
56
+ "simplify trig", "trig simplif", "trigonometric simplif",
57
+ ]
58
+
59
+ # ── Simple arithmetic detection ───────────────────────────────────────────────
60
+ MATH_PATTERN = re.compile(
61
+ r"""
62
+ ^ # start of string
63
+ \s* # optional leading whitespace
64
+ [\d\s\(\)] # starts with digit, space, or parenthesis
65
+ [\d\s\+\-\*\/\%\^\(\)\.]* # followed by math characters
66
+ $ # end of string
67
+ """,
68
+ re.VERBOSE,
69
+ )
70
+
71
+ ARITHMETIC_PREFIXES = ("calculate", "compute", "evaluate", "what is")
72
+
73
+ # ── Knowledge detection ───────────────────────────────────────────────────────
74
+ KNOWLEDGE_KEYWORDS = [
75
+ "what is", "what are", "who is", "who are", "explain", "define",
76
+ "tell me about", "describe", "how does", "why is", "when was",
77
+ "where is", "history of", "meaning of", "difference between",
78
+ "knowledge", "information about", "learn about", "facts about",
79
+ ]
80
+
81
+ # ── System commands ───────────────────────────────────────────────────────────
82
+ SYSTEM_PATTERN = re.compile(r"^/\w+")
83
+
84
+
85
+ def classify_intent(user_input: str) -> str:
86
+ """
87
+ Classify user input and return one of:
88
+ 'system' | 'advanced_math' | 'math' | 'knowledge' | 'conversation'
89
+ """
90
+ text = user_input.strip()
91
+ lowered = text.lower()
92
+
93
+ # 1. System commands (/exit, /help, /history)
94
+ if SYSTEM_PATTERN.match(text):
95
+ return "system"
96
+
97
+ # 2. Advanced math β€” symbolic operations (checked before simple math)
98
+ for kw in ADVANCED_MATH_KEYWORDS:
99
+ if kw in lowered:
100
+ return "advanced_math"
101
+
102
+ # 3. Simple arithmetic β€” numbers and operators only
103
+ if _is_simple_arithmetic(text, lowered):
104
+ return "math"
105
+
106
+ # 4. Knowledge questions
107
+ for kw in KNOWLEDGE_KEYWORDS:
108
+ if kw in lowered:
109
+ return "knowledge"
110
+
111
+ # 5. Fallback β€” conversation / LLM
112
+ return "conversation"
113
+
114
+
115
+ def _is_simple_arithmetic(text: str, lowered: str) -> bool:
116
+ """True if the input is a plain numeric arithmetic expression."""
117
+ # Allow optional natural-language prefix before checking expression
118
+ remainder = lowered
119
+ for prefix in ARITHMETIC_PREFIXES:
120
+ if lowered.startswith(prefix):
121
+ remainder = lowered[len(prefix):].strip()
122
+ break
123
+
124
+ # Must have at least one digit and one operator
125
+ has_digit = any(ch.isdigit() for ch in remainder)
126
+ has_operator = any(ch in "+-*/%^" for ch in remainder)
127
+ # Must NOT contain letters beyond simple operators (no variables)
128
+ has_letters = bool(re.search(r'[a-zA-Z]', remainder))
129
+
130
+ if has_digit and has_operator and not has_letters:
131
+ return True
132
+
133
+ # Also match pure numeric patterns via regex
134
+ return bool(MATH_PATTERN.match(text))