Upload 13 files
Browse files- advanced_math_engine.py +994 -0
- anveshai_memory.db +0 -0
- conversation.txt +23 -0
- conversation_engine.py +68 -0
- knowledge.txt +29 -0
- knowledge_engine.py +122 -0
- llm_engine.py +158 -0
- main.py +323 -0
- math_engine.py +148 -0
- memory.py +99 -0
- reasoning_engine.py +599 -0
- requirements.txt +4 -0
- router.py +134 -0
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))
|