diff --git "a/all_code.txt" "b/all_code.txt" deleted file mode 100644--- "a/all_code.txt" +++ /dev/null @@ -1,7152 +0,0 @@ - -================================================================================ -FILE: e:\madhuri\mathminds\check_agent.py -================================================================================ - - -try: - from google.adk.agents import Agent - print("Agent class found in google.adk.agents") -except ImportError: - print("Agent class NOT found in google.adk.agents") - -try: - from google.adk.agents import LlmAgent - print("LlmAgent class found in google.adk.agents") -except ImportError: - print("LlmAgent class NOT found in google.adk.agents") - - -================================================================================ -FILE: e:\madhuri\mathminds\debug_adk.py -================================================================================ - - -import sys -try: - import google - print("google imported") - print(dir(google)) - - try: - import google.adk - print("google.adk imported") - print(dir(google.adk)) - except ImportError as e: - print(f"Failed to import google.adk: {e}") - - try: - from google import adk - print("from google import adk succeeded") - print(dir(adk)) - except ImportError as e: - print(f"Failed to from google import adk: {e}") - -except ImportError as e: - print(f"Failed to import google: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\debug_env.py -================================================================================ - - -import sys -import os - -print(f"Python Executable: {sys.executable}") -print(f"Python Version: {sys.version}") -print(f"Sys Path: {sys.path}") - -try: - import langchain - print(f"LangChain Version: {langchain.__version__}") - print(f"LangChain Path: {langchain.__file__}") -except ImportError as e: - print(f"ImportError: {e}") -except Exception as e: - print(f"Error: {e}") - -# Verify Agent Import -try: - from app.agents.langchain_mathminds import MathMindsLangChainAgent - print("✅ MathMindsLangChainAgent imported.") -except ImportError as e: - print(f"Agent Import Failed: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\debug_import.py -================================================================================ - - -print("Start") -try: - import app.tools.web_scraper - print("Imported WebScraper") -except Exception as e: - print(f"Failed WebScraper: {e}") - -try: - import app.tools.vision_analyzer - print("Imported VisionAnalyzer") -except Exception as e: - print(f"Failed VisionAnalyzer: {e}") - -try: - from app.core.orchestrator import Orchestrator - print("Imported Orchestrator") - o = Orchestrator() - print("Instantiated Orchestrator") -except Exception as e: - print(f"Failed Orchestrator: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\debug_models.py -================================================================================ - -import os -import asyncio -from google import genai -from dotenv import load_dotenv - -load_dotenv() - -async def list_models(): - api_key = os.getenv("GOOGLE_API_KEY") - if not api_key: - print("Error: GOOGLE_API_KEY not found.") - return - - client = genai.Client(api_key=api_key) - - print("Listing available models...") - try: - # Pager object, need to iterate - pager = client.models.list() - for model in pager: - print(f"Name: {model.name}") - print(f" DisplayName: {model.display_name}") - print(f" Supported Actions: {model.supported_actions}") - print("-" * 20) - - except Exception as e: - print(f"Error listing models: {e}") - -if __name__ == "__main__": - asyncio.run(list_models()) - - -================================================================================ -FILE: e:\madhuri\mathminds\debug_scraper.py -================================================================================ - -from app.tools.web_scraper import run_playwright_sync - -query = "calculate the price of 2 kg gold according to todays gold rate" -print(f"Running scraper for: {query}") - -result = run_playwright_sync(query, headless=True) - -print("\n--- STATUS ---") -print(result.get("status")) - -print("\n--- CONTENT SNIPPET ---") -content = result.get("content", "") -# Print first 5000 chars to be sure we see the body -print(content[:5000]) - -if "unusual traffic" in content.lower() or "captcha" in content.lower(): - print("\n[!] DETECTED CAPTCHA/BLOCKING") -elif "Gold Rate" in content or "Silver Rate" in content: - print("\n[+] SUCCESS: Found Gold Rate related content") -else: - print("\n[?] Content unclear. Check snippet above.") - - -================================================================================ -FILE: e:\madhuri\mathminds\debug_scraper_manual.py -================================================================================ - -import asyncio -import sys -import os - -# Add project root to path -sys.path.append(os.getcwd()) - -from app.tools.web_scraper import WebScraper - -async def main(): - print("Initializing WebScraper...") - scraper = WebScraper(headless=True) - - print("\n--- Test 1: Generic Search (Yahoo Finance via Logic) ---") - # Logic in scraper: if "stock" in query -> yahoo finance - query1 = "stock price of apple" - print(f"Query: {query1}") - result1 = await scraper.scrape(query1) - print(f"Status: {result1.get('status')}") - if result1.get('error'): - print(f"Error: {result1.get('error')}") - else: - content = result1.get('content', '') - print(f"Content Length: {len(content)}") - print(f"Preview: {content[:200]}...") - - print("\n--- Test 2: Gold Rate (Goodreturns via Logic) ---") - # Logic in scraper: if "gold" and "rate" -> goodreturns - query2 = "gold rate today" - print(f"Query: {query2}") - result2 = await scraper.scrape(query2) - print(f"Status: {result2.get('status')}") - if result2.get('error'): - print(f"Error: {result2.get('error')}") - else: - content = result2.get('content', '') - print(f"Content Length: {len(content)}") - print(f"Preview: {content[:200]}...") - -if __name__ == "__main__": - asyncio.run(main()) - - -================================================================================ -FILE: e:\madhuri\mathminds\export_code.py -================================================================================ - -import os - -def collect_code(root_dir, output_file): - with open(output_file, 'w', encoding='utf-8') as outfile: - for root, dirs, files in os.walk(root_dir): - # Exclude directories - dirs[:] = [d for d in dirs if d not in ['.venv', 'venv', '__pycache__', '.git', '.gemini', '.agent']] - - for file in files: - if file.endswith('.py'): - file_path = os.path.join(root, file) - # Write Header - outfile.write(f"\n{'='*80}\n") - outfile.write(f"FILE: {file_path}\n") - outfile.write(f"{'='*80}\n\n") - - try: - with open(file_path, 'r', encoding='utf-8') as infile: - outfile.write(infile.read()) - outfile.write("\n") - except Exception as e: - outfile.write(f"Error reading file: {e}\n") - -if __name__ == "__main__": - # Run from project root ideally, or specify absolute path - project_root = r"e:\madhuri\mathminds" - output_path = os.path.join(project_root, "all_code.txt") - print(f"Scanning {project_root}...") - collect_code(project_root, output_path) - print(f"Done. Saved to {output_path}") - - -================================================================================ -FILE: e:\madhuri\mathminds\find_embedding_models.py -================================================================================ - -import os -from dotenv import load_dotenv -import google.generativeai as genai - -load_dotenv() -genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) - -print("Available models:") -for model in genai.list_models(): - if "gemini" in model.name: - print(f" {model.name:50} {model.display_name}") - -================================================================================ -FILE: e:\madhuri\mathminds\gunicorn_conf.py -================================================================================ - -import multiprocessing -import os - -# Binding -host = os.getenv("HOST", "0.0.0.0") -port = os.getenv("PORT", "8000") -bind = f"{host}:{port}" - -# Worker Options -# Default to (2 * cpu) + 1, but allow override -workers_default = multiprocessing.cpu_count() * 2 + 1 -workers = int(os.getenv("WORKERS", workers_default)) -worker_class = "uvicorn.workers.UvicornWorker" - -# Memory Leak Prevention -# Restart workers after a certain number of requests to limit memory leaks -max_requests = int(os.getenv("MAX_REQUESTS", 1000)) -max_requests_jitter = int(os.getenv("MAX_REQUESTS_JITTER", 50)) - -# Timeouts -timeout = int(os.getenv("TIMEOUT", 120)) # Higher timeout for GenAI calls/model loading -keepalive = int(os.getenv("KEEPALIVE", 5)) - -# Logging -accesslog = os.getenv("ACCESS_LOG", "-") -errorlog = os.getenv("ERROR_LOG", "-") -loglevel = os.getenv("LOG_LEVEL", "info") - -# Process Name -proc_name = "mathminds_api" - - -================================================================================ -FILE: e:\madhuri\mathminds\inspect_adk.py -================================================================================ - - -import os -import sys -from dotenv import load_dotenv - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -try: - from google.adk.agents import LlmAgent - import inspect - - print("=== LlmAgent.__init__ ===") - print(inspect.signature(LlmAgent.__init__)) - print(LlmAgent.__init__.__doc__) - - print("\n=== LlmAgent.run ===") - if hasattr(LlmAgent, 'run'): - print(inspect.signature(LlmAgent.run)) - print(LlmAgent.run.__doc__) - else: - print("No run method") - - print("\n=== google.adk.runners.Runner ===") - try: - from google.adk.runners import Runner - print(inspect.signature(Runner.__init__)) - print(Runner.__init__.__doc__) - - print("\n=== Runner.run_async ===") - if hasattr(Runner, 'run_async'): - print(inspect.signature(Runner.run_async)) - print(Runner.run_async.__doc__) - except ImportError: - print("Could not import google.adk.runners.Runner") - - print("\n=== google.adk.model.Model ===") - print("\n=== google.adk.sessions.in_memory_session_service.InMemorySessionService ===") - try: - from google.adk.sessions.in_memory_session_service import InMemorySessionService - print(dir(InMemorySessionService)) - print(inspect.signature(InMemorySessionService.create_session)) - except ImportError: - print("Could not import google.adk.sessions.in_memory_session_service.InMemorySessionService") - except Exception as e: - print(f"Error inspecting InMemorySessionService: {e}") - - try: - import google.genai.types - print("\n=== google.genai.types ===") - print("google.genai.types found") - except ImportError: - print("Could not import google.genai.types") - -except ImportError as e: - print(f"Failed to import: {e}") -except Exception as e: - print(f"Error: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\inspect_agent_class.py -================================================================================ - - -import inspect -from google.adk.agents import Agent - -print("=== Agent.__init__ ===") -print(inspect.signature(Agent.__init__)) -print(Agent.__init__.__doc__) - -print("\n=== Agent.run ===") -if hasattr(Agent, 'run'): - print(inspect.signature(Agent.run)) -else: - print("No run method") - -print("\n=== Agent.run_async ===") -if hasattr(Agent, 'run_async'): - print(inspect.signature(Agent.run_async)) -else: - print("No run_async method") - - -================================================================================ -FILE: e:\madhuri\mathminds\locate_adk_modules.py -================================================================================ - - -import pkgutil -import google.adk -import importlib - -def list_submodules(package, prefix): - print(f"package: {prefix}") - for loader, module_name, is_pkg in pkgutil.walk_packages(package.__path__, prefix + "."): - print(module_name) - if is_pkg: - try: - module = importlib.import_module(module_name) - # print(dir(module)) - except Exception as e: - print(f"Failed to import {module_name}: {e}") - -list_submodules(google.adk, "google.adk") - - -================================================================================ -FILE: e:\madhuri\mathminds\reproduce_crash.py -================================================================================ - - -import asyncio -import sys -import os -import logging -import traceback -from dotenv import load_dotenv - -# Load env vars -load_dotenv(override=True) - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -# Windows asyncio policy -if sys.platform == 'win32': - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - -from app.core.orchestrator import Orchestrator - -# Configure Logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("debugger") - -async def reproduce(): - print("Attempting to reproduce crash with 'solve 4x=36'...") - try: - orchestrator = Orchestrator() - - # Test case that crashed for user - force new hash - import time - query = f"solve 4x=36 {int(time.time())}" # Add timestamp to bypass cache - # Wait, adding timestamp breaks "solve" logic? - # "solve 4x=36 12345" might fail the regex? - # Better: Disable cache in settings temporarily? - # Or just clear cache first. - # Or just query = "solve 4x=36" and I clear cache in script. - - from app.core.settings import settings - settings.ENABLE_CACHE = False - - query = "solve 4x=36" - - print(f"Executing: {query}") - result = await orchestrator.process_problem(text=query) - - print("Finished without crash.") - print(f"Result: {result.get('answer')}") - - except Exception as e: - print(f"Exception caught in main loop: {e}") - traceback.print_exc() - -if __name__ == "__main__": - try: - asyncio.run(reproduce()) - except KeyboardInterrupt: - print("Interrupted") - except Exception as e: - print(f"Fatal crash: {e}") - traceback.print_exc() - - -================================================================================ -FILE: e:\madhuri\mathminds\test_lang.py -================================================================================ - -# quick test in python console or new file -from app.agents.langchain_mathminds import MathMindsLangChainAgent -import asyncio - -async def test(): - agent = MathMindsLangChainAgent() - result = await agent.solve("Solve 4x - 12 = 8") - print(result) - result2 = await agent.solve("What is the gold price today in India?") - print(result2) - -asyncio.run(test()) - -================================================================================ -FILE: e:\madhuri\mathminds\test_mongo_connection.py -================================================================================ - - -import os -import pymongo -from dotenv import load_dotenv - -# Force reload of .env -load_dotenv(override=True) - -uri = os.getenv("MONGO_URI") -print(f"Testing URI: {uri}") - -try: - # Need to handle potential "dnspython" requirement for SRV - client = pymongo.MongoClient(uri, serverSelectionTimeoutMS=5000) - info = client.server_info() - print("SUCCESS: Connected to MongoDB Atlas!") - print(f"Version: {info.get('version')}") -except Exception as e: - print(f"FAILURE: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\test_playwright.py -================================================================================ - -import asyncio -from playwright.async_api import async_playwright -import sys - -# Ensure policy is set here for isolation test -if sys.platform == 'win32': - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - -async def main(): - print("Starting Playwright...") - async with async_playwright() as p: - print("Launching Browser...") - browser = await p.chromium.launch(headless=True) - print("Browser Launched!") - page = await browser.new_page() - await page.goto("http://example.com") - print(await page.title()) - await browser.close() - print("Done.") - -if __name__ == "__main__": - asyncio.run(main()) - - -================================================================================ -FILE: e:\madhuri\mathminds\test_request.py -================================================================================ - -import requests -import json -import time - -url = "http://localhost:8001/solve" -# Read from payload.json -with open("payload.json", "r") as f: - payload = json.load(f) - -headers = { - "Content-Type": "application/json" -} - -start_time = time.time() -try: - print(f"Sending request to {url}...") - response = requests.post(url, json=payload, headers=headers) - print(f"Status Code: {response.status_code}") - print(f"Duration: {time.time() - start_time:.2f}s") - print("Response Body:") - print(response.text) -except Exception as e: - print(f"Error: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\test_sync_playwright.py -================================================================================ - -from playwright.sync_api import sync_playwright - -def run(): - print("Starting sync_playwright...") - try: - with sync_playwright() as p: - print("Launching browser...") - browser = p.chromium.launch(headless=True) - print("Browser launched.") - page = browser.new_page() - page.goto("http://example.com") - print("Title:", page.title()) - browser.close() - except Exception as e: - print(f"Error: {e}") - -if __name__ == "__main__": - run() - - -================================================================================ -FILE: e:\madhuri\mathminds\test_tool_wrapping.py -================================================================================ - - -from google.adk.agents import LlmAgent -from google.adk.tools import FunctionTool - -def my_tool(x: int) -> int: - """doubles x""" - return x * 2 - -try: - print("Trying to init LlmAgent with raw function...") - agent = LlmAgent( - name="test", - model="gemini-flash-latest", - instruction="test", - tools=[my_tool] - ) - print("Success! LlmAgent accepted raw function.") -except Exception as e: - print(f"Failed with raw function: {e}") - -try: - print("\nTrying to init LlmAgent with FunctionTool...") - agent = LlmAgent( - name="test", - model="gemini-flash-latest", - instruction="test", - tools=[FunctionTool(my_tool)] - ) - print("Success! LlmAgent accepted FunctionTool.") -except Exception as e: - print(f"Failed with FunctionTool: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\verify_phase1.py -================================================================================ - - -import sys -import os - -# Add project root to path -sys.path.append(os.getcwd()) - -print("Verifying Phase 1 Implementation...") - -try: - from app.agents.langchain_mathminds import MathMindsLangChainAgent - print("✅ MathMindsLangChainAgent imported successfully (Syntax Check Passed).") -except ImportError as e: - print(f"⚠️ Import Error for Agent (Likely missing dependencies): {e}") -except Exception as e: - print(f"❌ Syntax/Runtime Error in Agent: {e}") - -try: - from app.tools.data_processor import DataProcessor - print("✅ DataProcessor imported successfully (Syntax Check Passed).") -except ImportError as e: - print(f"⚠️ Import Error for DataProcessor (Likely missing dependencies): {e}") -except Exception as e: - print(f"❌ Syntax/Runtime Error in DataProcessor: {e}") - -try: - from app.tools.advanced_ocr import AdvancedOCR - print("✅ AdvancedOCR imported successfully (Syntax Check Passed).") -except ImportError as e: - print(f"⚠️ Import Error for AdvancedOCR (Likely missing dependencies): {e}") -except Exception as e: - print(f"❌ Syntax/Runtime Error in AdvancedOCR: {e}") - -print("\nVerification Complete. If you see 'Import Error', please run: pip install -r requirements.txt") - - -================================================================================ -FILE: e:\madhuri\mathminds\verify_phase2.py -================================================================================ - - -import sys -import os - -# Add project root to path -sys.path.append(os.getcwd()) - -print("Verifying Phase 2 Implementation...") - -try: - from app.models.vertex_gemini import VertexGeminiModel - print("✅ VertexGeminiModel imported successfully.") -except ImportError as e: - print(f"⚠️ Import Error for VertexGeminiModel: {e}") -except Exception as e: - print(f"❌ Error in VertexGeminiModel: {e}") - -try: - from app.database.firestore_client import FirestoreClient - print("✅ FirestoreClient imported successfully.") -except ImportError as e: - print(f"⚠️ Import Error for FirestoreClient: {e}") -except Exception as e: - print(f"❌ Error in FirestoreClient: {e}") - -print("\nVerification Complete.") - - -================================================================================ -FILE: e:\madhuri\mathminds\verify_phase3.py -================================================================================ - - -import sys -import os -import yaml - -# Add project root to path -sys.path.append(os.getcwd()) - -print("Verifying Phase 3 Implementation...") - -# 1. Verify Worker Import -try: - from app.worker import scrape_web_task - print("✅ Celery Task 'scrape_web_task' imported successfully.") -except ImportError as e: - print(f"❌ Import Error for app.worker: {e}") - -# 2. Verify Orchestrator Import (Static Check not easy, so we check if file content has changed) -# We trust the file write, but let's check docker-compose.yml -try: - with open("docker-compose.yml", "r") as f: - dc = yaml.safe_load(f) - - services = dc.get("services", {}) - if "worker" in services: - print("✅ Docker Compose: 'worker' service found.") - else: - print("❌ Docker Compose: 'worker' service MISSING.") - - if "n8n" in services: - print("✅ Docker Compose: 'n8n' service found.") - else: - print("❌ Docker Compose: 'n8n' service MISSING.") - -except Exception as e: - print(f"❌ Error reading docker-compose.yml: {e}") - -print("\nVerification Complete.") - - -================================================================================ -FILE: e:\madhuri\mathminds\verify_phase4.py -================================================================================ - - -import sys -import os - -# Add project root to path -sys.path.append(os.getcwd()) - -print("Verifying Phase 4 Implementation...") - -try: - import gradio_demo - print("✅ Gradio Demo imported successfully (Syntax Check Passed).") -except ImportError as e: - print(f"⚠️ Import Error for Gradio Demo (Likely missing dependencies): {e}") -except Exception as e: - print(f"❌ Syntax/Runtime Error in Gradio Demo: {e}") - -try: - from app.tools.selenium_scraper import SeleniumScraper - print("✅ SeleniumScraper imported successfully (Syntax Check Passed).") -except ImportError as e: - print(f"⚠️ Import Error for SeleniumScraper (Likely missing dependencies): {e}") -except Exception as e: - print(f"❌ Syntax/Runtime Error in SeleniumScraper: {e}") - -print("\nVerification Complete.") - - -================================================================================ -FILE: e:\madhuri\mathminds\verify_routing.py -================================================================================ - - -from app.core.orchestrator import Orchestrator - -def test_routing(): - orch = Orchestrator() - - # Test cases - queries = [ - ("calculate 2+2", True), # Simple - ("solve x+5=10", True), # Simple - ("what is the probability of drawing a red card?", False), # Complex (Probability) - ("integrate x^2", False), # Complex (Calculus) - ("calculate the sum of limits", False) # Complex (Limit) - ] - - print("--- Verifying Routing Logic ---") - all_passed = True - for q, expected_simple in queries: - is_simple = orch._is_simple_problem(q) - status = "✅" if is_simple == expected_simple else "❌" - if not is_simple == expected_simple: - all_passed = False - print(f"{status} Query: '{q}' -> Is Simple? {is_simple} (Expected: {expected_simple})") - - if all_passed: - print("\n✅ All routing checks passed!") - else: - print("\n❌ Use verify_routing.py to debug failures.") - -if __name__ == "__main__": - test_routing() - - -================================================================================ -FILE: e:\madhuri\mathminds\verify_symbolic.py -================================================================================ - - -import asyncio -import sys -import os -import logging -from dotenv import load_dotenv - -# Load env vars -load_dotenv(override=True) - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -if sys.platform == 'win32': - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - -from app.core.orchestrator import Orchestrator - -# Configure Logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("verifier") - -async def verify(): - print("🚀 Starting Symbolic Solver Verification...") - orchestrator = Orchestrator() - - test_cases = [ - "Calculate 152 * 139", # Unique - "Solve 4x + 12 = 32", # Unique - "derivative of x^4", # Unique - "Explain limits" # Should NOT be deterministic - ] - - for query in test_cases: - print(f"\n--- Testing: {query} ---") - try: - result = await orchestrator.process_problem(text=query) - - source = result.get("metadata", {}).get("source") - model = result.get("metadata", {}).get("model_used") - latency = result.get("metadata", {}).get("latency", 0) - answer = result.get("answer", {}) - - p_source = "✅" if source == "deterministic" else "❌" - if query == "Explain limits": - p_source = "✅" if source != "deterministic" else "❌" - - print(f"Source: {source} {p_source}") - print(f"Model: {model}") - print(f"Latency: {latency:.4f}s") - print(f"Answer Keys: {list(answer.keys())}") - print(f"Content: {answer.get('text') or answer.get('answer') or answer}") - - except Exception as e: - print(f"❌ Error: {e}") - -if __name__ == "__main__": - asyncio.run(verify()) - - -================================================================================ -FILE: e:\madhuri\mathminds\verify_timeout.py -================================================================================ - - -import asyncio -import sys -import os -import logging -import time -from dotenv import load_dotenv - -# Load env vars -load_dotenv(override=True) - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -if sys.platform == 'win32': - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - -from app.core.orchestrator import Orchestrator - -# Configure Logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("verifier") - -async def verify(): - print("Starting Timeout Verification...") - orchestrator = Orchestrator() - - # "3+5" should be instant. - # If it was blocking, this script would hang or take > 2s per call if we were simulating high load, - # but here we just want to ensure it RETURNS success and definitely < 2s. - - test_cases = [ - "calculate 100+55", # Unique - "solve 4z=20", # Unique - ] - - for query in test_cases: - print(f"\n--- Testing: {query} ---") - start = time.time() - try: - result = await orchestrator.process_problem(text=query) - duration = time.time() - start - - source = result.get("metadata", {}).get("source") - latency = result.get("metadata", {}).get("latency", 0) - answer = result.get("answer", {}) - - print(f"Source: {source} (Expected: deterministic/cache)") - print(f"Latency: {latency:.4f}s") - print(f"Total Duration: {duration:.4f}s") - - if duration > 2.5: - print("❌ FAILED: Took too long (Blocking?)") - else: - print("✅ PASSED: Fast execution") - - print(f"Content: {answer.get('text') or answer.get('answer') or answer}") - - except Exception as e: - print(f"❌ Error: {e}") - -if __name__ == "__main__": - asyncio.run(verify()) - - -================================================================================ -FILE: e:\madhuri\mathminds\app\worker.py -================================================================================ - -import os -import asyncio -from celery import Celery -from app.core.settings import settings -from app.tools.web_scraper import WebScraper -import logging - -# Configure Logging -logger = logging.getLogger(__name__) - -# Initialize Celery -celery_app = Celery( - "mathminds_worker", - broker=settings.REDIS_URL, - backend=settings.REDIS_URL -) - -celery_app.conf.update( - task_serializer="json", - accept_content=["json"], - result_serializer="json", - timezone="UTC", - enable_utc=True, -) - -@celery_app.task(name="scrape_web_task", bind=True) -def scrape_web_task(self, query: str, focus: str = ""): - """ - Celery task to run web scraping in a background worker. - Since Playwright is async/sync hybrid, we run the sync version here - or manage the loop carefully. - """ - logger.info(f"Worker: Starting scrape for '{query}'") - - # We use the sync logic of the scraper tools or run the async one via asyncio.run - # For simplicity/stability in Celery, we'll instantiate the scraper and run. - - # Note: WebScraper class uses ProcessPoolExecutor internally for safety on Windows - # Here we are already in a worker process, so we can just run it. - - scraper = WebScraper(headless=True) - - # Run async scrape in this sync task - try: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - result = loop.run_until_complete(scraper.scrape(query, extraction_focus=focus)) - loop.close() - return result - except Exception as e: - logger.error(f"Worker Scrape Failed: {e}") - return {"error": str(e), "status": "error"} - -@celery_app.task(name="solve_heavy_math_task") -def solve_heavy_math_task(problem_text: str): - """ - Placeholder for really heavy symbolic computation if needed. - """ - pass - - -================================================================================ -FILE: e:\madhuri\mathminds\app\__init__.py -================================================================================ - - - -================================================================================ -FILE: e:\madhuri\mathminds\app\agents\adk_mathminds.py -================================================================================ - - -import logging -import asyncio -import base64 -from typing import Optional, Dict, Any, List - -from google.adk.agents import Agent -from google.adk.runners import Runner -from google.adk.sessions.in_memory_session_service import InMemorySessionService -from google.genai import types - -from app.core.settings import settings -from app.tools.web_scraper import WebScraper -from app.tools.symbolic_solver import SymbolicSolver -from app.tools.similarity_search import SimilarProblemFinder -from app.core.math_normalizer import MathQueryNormalizer - -logger = logging.getLogger(__name__) - -class MathMindsADKAgent: - """ - Agent-based architecture using Google ADK (GitHub version). - Refined to match official Multitool Agent documentation patterns. - """ - - def __init__(self, model_name: str = "gemini-2.5-flash"): - self.api_key = settings.GOOGLE_API_KEY - if not self.api_key: - logger.warning("No Google API Key found. Agent will fail.") - - # Initialize Tool Instances - self.web_scraper = WebScraper(headless=True) - self.symbolic_solver = SymbolicSolver() - self.normalizer = MathQueryNormalizer() - self.similar_finder = SimilarProblemFinder() - - # Define Tools as simpler closures - # Docs pattern: simple functions, passed in a list. - async def web_search(query: str) -> str: - """ - Useful for finding current events, prices, weather, and general information from the internet. - - Args: - query: The search query. - """ - result = await self.web_scraper.scrape(query) - if result.get("status") == "success": - return result.get("content", "No content found.") - else: - return f"Error searching web: {result.get('error')}" - - def math_solver(problem: str) -> str: - """ - Useful for solving symbolic math problems like equations, derivatives, integrals, and simplification. - - Args: - problem: The math problem description or expression. - """ - intent = self.normalizer.normalize(problem) - query_obj = intent if intent else problem - result = self.symbolic_solver.solve(query_obj) - - if result.get("status") == "success": - return result.get("content", "No solution found.") - else: - return f"Error solving math: {result.get('error')}" - - def find_similar_problems(query: str) -> str: - """ - Useful for finding similar math problems and their solutions from the database to learn how they were solved. - Use this when you are stuck or want to see examples. - - Args: - query: The math problem to find similar examples for. - """ - results = self.similar_finder.search(query, limit=2) - if not results: - return "No similar problems found." - - formatted = "Here are some similar problems and their solutions:\n" - for item in results: - formatted += f"Problem: {item.get('problem_text')}\nSolution: {item.get('solution_text')}\n---\n" - return formatted - - # Initialize Agent - # Using 'Agent' class as per official docs, passing functions directly. - self.agent = Agent( - name="math_minds_core", - model=model_name, - tools=[web_search, math_solver, find_similar_problems], # Passed directly as function list - instruction=( - "You are MathMinds AI, a helpful and precise mathematical assistant. " - "You have access to tools for solving symbolic math problems, searching the web, and finding similar solved problems. " - "If an image is provided, analyze it mathematically. " - "Use 'Math Solver' for distinct math problems (equations, calculus, etc.). " - "Use 'Web Search' for real-world data (prices, weather, facts). " - "Use 'Find Similar Problems' to look up examples if you are unsure how to solve a problem. " - "Always explain your steps clearly." - ) - ) - - # Session Service - self.session_service = InMemorySessionService() - - # Runner - self.runner = Runner( - app_name="mathminds", - agent=self.agent, - session_service=self.session_service - ) - - logger.info("MathMindsADKAgent initialized successfully (Doc Standard).") - - async def solve(self, problem: str, image_data: Optional[str] = None, session_id: str = "default_session", user_id: str = "default_user") -> str: - """ - Main entry point for the agent to solve a problem. - """ - # IDs are now passed in, with fallbacks for backward compatibility - - try: - # Ensure session exists (create if not found) - try: - existing = await self.session_service.get_session( - app_name="mathminds", session_id=session_id, user_id=user_id - ) - if not existing: - await self.session_service.create_session( - app_name="mathminds", user_id=user_id, session_id=session_id - ) - except Exception: - try: - await self.session_service.create_session( - app_name="mathminds", user_id=user_id, session_id=session_id - ) - except Exception as create_err: - logger.warning(f"Session creation issue (might already exist): {create_err}") - - # Construct Message Parts - parts = [] - parts.append(types.Part.from_text(text=problem)) - - if image_data: - try: - img_bytes = base64.b64decode(image_data) - mime_type = "image/png" - if image_data.startswith("/9j/"): - mime_type = "image/jpeg" - - parts.append(types.Part.from_bytes(data=img_bytes, mime_type=mime_type)) - logger.info("Attached image to agent request.") - except Exception as e: - logger.error(f"Failed to process image data: {e}") - parts.append(types.Part.from_text(text="[Error: attached image could not be processed]")) - - # Execute Agent - response_text = "" - async for event in self.runner.run_async( - user_id=user_id, - session_id=session_id, - new_message=types.Content(role="user", parts=parts) - ): - if event.content and event.content.parts: - for part in event.content.parts: - if part.text: - response_text += part.text - - return response_text - - except Exception as e: - logger.error(f"ADK Agent execution failed: {e}") - return f"Error processing request: {str(e)}" - - -================================================================================ -FILE: e:\madhuri\mathminds\app\agents\__init__.py -================================================================================ - - - -================================================================================ -FILE: e:\madhuri\mathminds\app\api\deps.py -================================================================================ - -import os -import redis -import pymongo -from functools import lru_cache -from typing import Optional -import logging - -from app.core.orchestrator import Orchestrator -from app.memory.cache import CacheManager -from app.memory.database import DatabaseManager - -from app.core.settings import settings - -logger = logging.getLogger(__name__) - -# --- Connection Pools (Global Singletons) --- -# We use global variables instead of lru_cache for connections so we can reset them if needed -_redis_pool: Optional[redis.ConnectionPool] = None -_mongo_client: Optional[pymongo.MongoClient] = None - -def get_redis_pool() -> redis.ConnectionPool: - """ - Creates a shared Redis connection pool. - Not cached with lru_cache to avoid caching failed states or stale configs forever. - Uses a global singleton pattern with lazy validation. - """ - global _redis_pool - if _redis_pool: - return _redis_pool - - try: - redis_url = settings.REDIS_URL - if not redis_url: - raise ValueError("REDIS_URL is not set.") - - pool = redis.ConnectionPool.from_url(redis_url, decode_responses=True) - - # Optional: Fail fast check - # r = redis.Redis(connection_pool=pool) - # r.ping() - - _redis_pool = pool - logger.info(f"Initialized Redis Pool: {redis_url}") - return _redis_pool - except Exception as e: - logger.error(f"Failed to create Redis connection pool: {e}") - raise - -def get_mongo_client() -> pymongo.MongoClient: - """ - Creates a shared MongoDB client. - """ - global _mongo_client - if _mongo_client: - return _mongo_client - - try: - mongo_uri = settings.MONGO_URI - client = pymongo.MongoClient( - mongo_uri, - serverSelectionTimeoutMS=5000, - minPoolSize=1, - maxPoolSize=50 - ) - _mongo_client = client - logger.info("Initialized MongoDB Client") - return _mongo_client - except Exception as e: - logger.error(f"Failed to create Mongo client: {e}") - raise - -# --- Component Factories --- - -@lru_cache() -def get_cache_manager() -> CacheManager: - return CacheManager(connection_pool=get_redis_pool()) - -@lru_cache() -def get_db_manager() -> DatabaseManager: - return DatabaseManager(client=get_mongo_client()) - -from threading import Lock - -_orchestrator: Optional[Orchestrator] = None -_orchestrator_lock = Lock() - -def get_orchestrator() -> Orchestrator: - """ - Thread-safe Singleton provider for Orchestrator. - Ensures heavy models are loaded exactly once per process. - """ - global _orchestrator - if _orchestrator: - return _orchestrator - - with _orchestrator_lock: - # Double-check locking - if _orchestrator: - return _orchestrator - - logger.info("Initializing Orchestrator Singleton...") - _orchestrator = Orchestrator( - cache_manager=get_cache_manager(), - db_manager=get_db_manager() - ) - return _orchestrator - - -================================================================================ -FILE: e:\madhuri\mathminds\app\api\main.py -================================================================================ - - -import sys -import asyncio - -if sys.platform == 'win32': - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - -import logging -import datetime -from datetime import datetime -import uuid -import sys - -from fastapi import FastAPI, HTTPException, status, Depends, Request -from fastapi.responses import JSONResponse -from slowapi import _rate_limit_exceeded_handler -from slowapi.errors import RateLimitExceeded -from app.core.limiter import limiter -from app.core.orchestrator import Orchestrator -from app.core.schemas import SolveRequest, SolveResponse, HealthResponse -from app.core.logging_config import configure_logging -from app.core.errors import AppError, ErrorCodes, ERROR_MESSAGES -from app.core.settings import settings # New settings module -import os -# Import dependency -from app.api.deps import get_orchestrator, get_redis_pool, get_mongo_client, get_db_manager -from app.core.security import verify_token, get_current_user - -from contextlib import asynccontextmanager - -# Configure logging -configure_logging() -logger = logging.getLogger(__name__) - -@asynccontextmanager -async def lifespan(app: FastAPI): - # Startup: Preload resources - logger.info("🚀 Starting MathMinds AI... Warming up resources.") - - try: - # 1. Initialize Redis Pool - get_redis_pool() - - # 2. Initialize MongoDB Client - get_mongo_client() - - # 3. Initialize Orchestrator (Loads YOLO, Supabase, etc.) - # This is the heavy lifting - get_orchestrator() - - logger.info("✅ Startup complete: Orchestrator & DBs ready.") - except Exception as e: - logger.critical(f"❌ Critical Startup Error: {e}") - # We might want to exit here, but let's allow it to run in degraded mode - # or let the first request fail. - - yield - - # Shutdown: Cleanup if needed - logger.info("🛑 Shutting down MathMinds AI...") - # (Optional) Close connections here if we implemented close methods - -app = FastAPI( - title="MathMinds AI API", - description="API for solving math problems using Gemini and local reasoning.", - version="1.0.0", - lifespan=lifespan -) - -# Global Exception Handler (Catch-All) -@app.exception_handler(Exception) -async def global_exception_handler(request: Request, exc: Exception): - request_id = getattr(request.state, "request_id", "unknown") - logger.error(f"[{request_id}] Unhandled Exception: {str(exc)}", exc_info=True) - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content={ - "status": "error", - "error": "Internal Server Error", - "error_code": "INTERNAL_ERROR", - "metadata": { - "request_id": request_id, - "timestamp": datetime.utcnow().isoformat() - } - } - ) - -# Initialize Rate Limiter -app.state.limiter = limiter -app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) - -@app.exception_handler(AppError) -async def app_error_handler(request: Request, exc: AppError): - """Handle application-level errors with proper HTTP status codes.""" - - # Map error codes to HTTP status codes - error_to_status = { - ErrorCodes.INPUT_VALIDATION_ERROR: status.HTTP_400_BAD_REQUEST, - ErrorCodes.RESOURCE_NOT_FOUND: status.HTTP_404_NOT_FOUND, - ErrorCodes.DEPENDENCY_ERROR: status.HTTP_503_SERVICE_UNAVAILABLE, - ErrorCodes.GEMINI_ERROR: status.HTTP_503_SERVICE_UNAVAILABLE, - ErrorCodes.RATE_LIMIT_EXCEEDED: status.HTTP_429_TOO_MANY_REQUESTS, - ErrorCodes.INTERNAL_ERROR: status.HTTP_500_INTERNAL_SERVER_ERROR, - } - - http_status = error_to_status.get(exc.code, status.HTTP_500_INTERNAL_SERVER_ERROR) - - request_id = getattr(request.state, "request_id", "unknown") - - logger.error(f"[{request_id}] AppError: {exc.code} - {exc.message}") - - return JSONResponse( - status_code=http_status, - content={ - "status": "error", - "error": exc.message, - "error_code": exc.code, - "metadata": { - "request_id": request_id, - "timestamp": datetime.utcnow().isoformat() - } - } - ) - -@app.middleware("http") -async def add_request_id(request: Request, call_next): - request_id = str(uuid.uuid4()) - request.state.request_id = request_id - - # Context for logging - log_context = {"request_id": request_id, "path": request.url.path, "method": request.method} - - logger.info("Request started", extra=log_context) - - import time - start_time = time.time() - - response = await call_next(request) - - duration = time.time() - start_time - response.headers["X-Request-ID"] = request_id - - logger.info("Request finished", extra={ - **log_context, - "status_code": response.status_code, - "duration": duration - }) - - return response - -@app.get("/health") -async def health_check(): - """Detailed health check endpoint.""" - health_status = { - "status": "healthy", - "version": "1.0.0", - "timestamp": datetime.utcnow().isoformat(), - "components": {} - } - - # Check Redis - try: - redis_pool = get_redis_pool() - if redis_pool: - conn = redis_pool.get_connection('health_check') - conn.ping() - redis_pool.release(conn) - health_status["components"]["redis"] = "✓ healthy" - else: - health_status["components"]["redis"] = "✗ unavailable" - except Exception as e: - health_status["components"]["redis"] = f"✗ error: {str(e)}" - - # Check MongoDB - try: - mongo_client = get_mongo_client() - if mongo_client: - # Low timeout ping - mongo_client.admin.command('ping') - health_status["components"]["mongodb"] = "✓ healthy" - else: - health_status["components"]["mongodb"] = "✗ unavailable" - except Exception as e: - health_status["components"]["mongodb"] = f"✗ error: {str(e)}" - - # Check Gemini - try: - # Just verify we have API key - api_key = os.getenv("GOOGLE_API_KEY") - if api_key: - health_status["components"]["gemini"] = "✓ configured" - else: - health_status["components"]["gemini"] = "✗ not configured" - except Exception as e: - health_status["components"]["gemini"] = f"✗ error: {str(e)}" - - # Overall status - if any("✗" in str(v) for v in health_status["components"].values()): - health_status["status"] = "degraded" - - return health_status - -@app.post("/solve", response_model=SolveResponse) -@limiter.limit("5/minute") -async def solve_problem( - request: Request, - solve_req: SolveRequest, - orchestrator: Orchestrator = Depends(get_orchestrator), - current_user: dict = Depends(get_current_user) # Protect this route -): - """ - Solves a mocked problem provided in the request body. - """ - # Grab request_id from state - req_id = getattr(request.state, "request_id", str(uuid.uuid4())) - - if not orchestrator: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="Orchestrator not initialized" - ) - - try: - result = await orchestrator.process_problem( - text=solve_req.effective_text, - image=solve_req.image, - request_id=req_id, - model_preference=solve_req.model_preference, - session_id=solve_req.session_id, - user_id=current_user.get("uid") - ) - - # Sanitize metadata for public response - public_metadata = result["metadata"].copy() - public_metadata.pop("_internal_debug", None) - - # Map internal result to schema - # Map internal result to schema - return SolveResponse( - request_id=result.get("request_id", req_id), - status=result["status"], - problem_type=result.get("problem_type", "unknown"), - source=result.get("source", "unknown"), - answer=result.get("answer"), - steps=result.get("steps", []), - explanation=result.get("explanation"), - confidence=result.get("confidence", 0.0), - cached=result.get("cached", False), - error=result.get("error_msg"), # Keep for backward compat if any - error_code=result.get("error_code"), - metadata=public_metadata - ) - - except Exception as e: - logger.error(f"[{req_id}] Unhandled error in /solve: {e}") - # Return generic error - return JSONResponse( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content={ - "status": "error", - "error": ERROR_MESSAGES[ErrorCodes.INTERNAL_ERROR], - "error_code": ErrorCodes.INTERNAL_ERROR, - "metadata": {"request_id": req_id} - } - ) - -# --- User Profile Endpoints --- -from pydantic import BaseModel -from typing import List, Optional - -class UserProfileUpdate(BaseModel): - display_name: Optional[str] = None - math_level: Optional[str] = "Student" - interests: Optional[List[str]] = [] - -@app.get("/users/profile") -async def get_profile( - current_user: dict = Depends(get_current_user), - db_manager = Depends(get_db_manager) -): - """Get current user profile.""" - try: - profile = db_manager.get_user_profile(current_user["uid"]) - if not profile: - # Return basic info if no profile exists yet - return { - "user_id": current_user["uid"], - "email": current_user.get("email"), - "display_name": "", - "math_level": "Student", - "interests": [], - "is_new": True - } - - # Remove MongoDB _id - if "_id" in profile: - del profile["_id"] - return profile - except Exception as e: - logger.error(f"Profile fetch error: {e}") - raise HTTPException(status_code=500, detail="Failed to fetch profile") - -@app.post("/users/profile") -async def update_profile( - profile_data: UserProfileUpdate, - current_user: dict = Depends(get_current_user), - db_manager = Depends(get_db_manager) -): - """Update user profile.""" - try: - success = db_manager.update_user_profile(current_user["uid"], profile_data.dict(exclude_unset=True)) - if not success: - raise HTTPException(status_code=500, detail="Failed to update profile") - return {"status": "success", "profile": profile_data.dict()} - except Exception as e: - logger.error(f"Profile update error: {e}") - raise HTTPException(status_code=500, detail=str(e)) - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8000) - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\errors.py -================================================================================ - -from enum import Enum - -class ErrorCodes(str, Enum): - INTERNAL_ERROR = "ERR_001" - INPUT_VALIDATION_ERROR = "ERR_002" - RESOURCE_NOT_FOUND = "ERR_003" - DEPENDENCY_ERROR = "ERR_004" - GEMINI_ERROR = "ERR_005" - RATE_LIMIT_EXCEEDED = "ERR_006" - -ERROR_MESSAGES = { - ErrorCodes.INTERNAL_ERROR: "An unexpected internal error occurred.", - ErrorCodes.INPUT_VALIDATION_ERROR: "The input provided is invalid.", - ErrorCodes.RESOURCE_NOT_FOUND: "The requested resource was not found.", - ErrorCodes.DEPENDENCY_ERROR: "A downstream service is currently unavailable.", - ErrorCodes.GEMINI_ERROR: "Unable to generate a solution at this time.", - ErrorCodes.RATE_LIMIT_EXCEEDED: "Too many requests. Please try again later." -} - -class AppError(Exception): - def __init__(self, code: ErrorCodes, message: str = None, original_exception: Exception = None): - self.code = code - self.message = message or ERROR_MESSAGES.get(code, "Unknown Error") - self.original_exception = original_exception - super().__init__(self.message) - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\input_processor.py -================================================================================ - -import base64 -import enum -import re -import urllib.parse -from dataclasses import dataclass -from typing import Optional, Tuple, Dict, Any -from app.core.ocr import OCRProcessor - -class InputType(enum.Enum): - """Enumeration of supported input types.""" - TEXT = "text" - LATEX = "latex" - IMAGE_URL = "image_url" - BASE64_IMAGE = "base64_image" - MULTIMODAL = "multimodal" - UNKNOWN = "unknown" - -# ... (omitted dataclass, no changes needed there) ... - - - -@dataclass -class ProcessingResult: - """Result of the input processing pipeline.""" - input_type: InputType - cleaned_content: str - is_valid: bool - error_message: Optional[str] = None - metadata: Optional[Dict[str, Any]] = None - -class InputProcessor: - """ - Handles detection, normalization, and validation of user inputs. - - Attributes: - max_length (int): Maximum allowed characters for text inputs. - """ - - def __init__(self, max_length: int = 5000): - """ - Initialize the InputProcessor. - - Args: - max_length: Maximum allowed length for input strings. Defaults to 5000. - """ - self.max_length = max_length - # Basic SQL injection and script tag patterns - self._dangerous_patterns = [ - re.compile(r".*?", re.IGNORECASE | re.DOTALL), - re.compile(r"javascript:", re.IGNORECASE), - re.compile(r"union\s+select", re.IGNORECASE), - re.compile(r"drop\s+table", re.IGNORECASE), - re.compile(r"exec\s*\(", re.IGNORECASE), - ] - self.ocr_processor = OCRProcessor() - - def process_compound(self, text_input: Optional[str] = None, image_input: Optional[str] = None) -> ProcessingResult: - """ - Process combined text and image input. - - Args: - text_input: Optional text query. - image_input: Optional image (Base64 or URL). - - Returns: - ProcessingResult: Combined result. - """ - cleaned_text = "" - image_data = None - detected_type = InputType.UNKNOWN - error_msg = None - - # 1. Process Image if present - if image_input: - # Detect if URL or Base64 (naive check) - if image_input.startswith("http") and "://" in image_input: - # URL -> Download - image_data = self.ocr_processor.download_image_as_base64(image_input) - if not image_data: - return ProcessingResult(InputType.IMAGE_URL, "", False, "Failed to download image.") - detected_type = InputType.IMAGE_URL # Or promote to MULTIMODAL later - else: - # Assume Base64 - # Strip prefix if needed - if ";base64," in image_input: - _, raw_b64 = image_input.split(";base64,") - else: - raw_b64 = image_input.strip() - - # Basic validation? - if len(raw_b64) < 10: - return ProcessingResult(InputType.BASE64_IMAGE, "", False, "Invalid image data.") - - image_data = raw_b64 - detected_type = InputType.BASE64_IMAGE - - # 2. Process Text if present - if text_input: - cleaned_text = self._normalize_text(text_input) - - # If we also have an image, it's MULTIMODAL. - # CRITICAL: We MUST preserve the text input as it provides specific context (e.g. "Solve part b") - if image_data: - detected_type = InputType.MULTIMODAL - elif detected_type == InputType.UNKNOWN: - # Text only, refined detection (latex vs text) - detected_type = self._detect_type(cleaned_text) - - # 3. Final Validation - if not cleaned_text and not image_data: - return ProcessingResult(InputType.UNKNOWN, "", False, "No valid input provided.") - - metadata = {} - if image_data: - metadata["image_data"] = image_data - - # Validate text content if present (length, safety) - if cleaned_text: - is_valid, err = self._validate(cleaned_text, detected_type) - if not is_valid: - return ProcessingResult(detected_type, cleaned_text, False, err) - - return ProcessingResult( - input_type=detected_type, - cleaned_content=cleaned_text, - is_valid=True, - metadata=metadata - ) - - def process(self, input_data: str) -> ProcessingResult: - """ - Process the raw input string: detect type, normalize, and validate. - - Args: - input_data: The raw input string from the user. - - Returns: - ProcessingResult: The processed and validated result. - """ - if not input_data: - return ProcessingResult(InputType.UNKNOWN, "", False, "Input cannot be empty.") - - metadata = None - detected_type = self._detect_type(input_data) - - if detected_type in (InputType.TEXT, InputType.LATEX): - cleaned_content = self._normalize_text(input_data) - elif detected_type == InputType.BASE64_IMAGE: - # Process Base64 - # Store raw base64 for Vision Model (strip prefix) - try: - if ";base64," in input_data: - _, raw_b64 = input_data.split(";base64,") - else: - raw_b64 = input_data - except ValueError: - return ProcessingResult(detected_type, "", False, "Invalid base64 image format.") - - # Skip OCR for Base64 images to avoid "double work" and latency. - # We rely on Gemini Vision to read the image directly. - # cleaned_content is set to empty string; hashing will rely on image_data hash. - extracted_text = "" - - # Attach image data to result - # Optimize image (resize/compress) to reduce token count and bandwidth - optimized_b64 = self.ocr_processor.optimize_base64(raw_b64) - - metadata = {"image_data": optimized_b64} - cleaned_content = "" - elif detected_type == InputType.IMAGE_URL: - # Process URL: Download and pass as image to Vision (Skip OCR) - # extracted_text = self.ocr_processor.process_url(input_data) - - raw_b64 = self.ocr_processor.download_image_as_base64(input_data) - - if not raw_b64: - return ProcessingResult(detected_type, "", False, "Failed to download image from URL.") - - # Attach image data (Vision will use this) - metadata = {"image_data": raw_b64} - cleaned_content = "" - else: - cleaned_content = input_data.strip() - metadata = None - - is_valid, error_msg = self._validate(cleaned_content, detected_type) - - return ProcessingResult( - input_type=detected_type, - cleaned_content=cleaned_content, - is_valid=is_valid, - error_message=error_msg, - metadata=metadata - ) - - def _detect_type(self, data: str) -> InputType: - """ - Detect the type of the input data. - - Args: - data: The raw input string. - - Returns: - InputType: The detected input type. - """ - data = data.strip() - - # Check for Base64 Image - # A simple heuristic: starts with data:image/ or looks like base64 - if data.startswith("data:image/") and ";base64," in data: - return InputType.BASE64_IMAGE - - # Check for Image URL - parsed_url = urllib.parse.urlparse(data) - if parsed_url.scheme in ("http", "https") and any( - data.lower().endswith(ext) for ext in [".jpg", ".jpeg", ".png", ".gif", ".webp"] - ): - return InputType.IMAGE_URL - - # Check for LaTeX - # Heuristic: contains mathematical delimiters or keywords - if ( - "$" in data - or "\\[" in data - or "\\(" in data - or re.search(r"\\[a-zA-Z]+", data) - ): - return InputType.LATEX - - # Default to text - return InputType.TEXT - - def _normalize_text(self, text: str) -> str: - """ - Normalize text input: lowercase, trim, remove extra spaces. - - Args: - text: The text to normalize. - - Returns: - str: Normalized text. - """ - # Lowercase - text = text.lower() - - # Remove extra horizontal whitespace (tabs, multiple spaces) - text = re.sub(r'[ \t]+', ' ', text) - - # Collapse multiple newlines into one - text = re.sub(r'\n+', '\n', text) - - return text.strip() - - def _remove_ocr_artifacts(self, text: str) -> str: - """Remove common OCR extraction errors.""" - # Remove repeated characters (OCR artifact) - text = re.sub(r'([!?.\-_=])\1{3,}', r'\1\1', text) - - # Remove random special chars at start/end - # Added '-' to the allowed list to prevent stripping negative numbers - text = re.sub(r'^[^a-zA-Z0-9\(\[\{$\-]*', '', text) - text = re.sub(r'[^a-zA-Z0-9\)\]\}\$]*$', '', text) - - return text - - def _validate(self, content: str, input_type: InputType) -> Tuple[bool, Optional[str]]: - """ - Validate the content based on its type and generic safety rules. - - Args: - content: The content to validate. - input_type: The type of the content. - - Returns: - Tuple[bool, Optional[str]]: (IsValid, ErrorMessage) - """ - if len(content) > self.max_length and input_type != InputType.BASE64_IMAGE: - return False, f"Input length exceeds maximum limit of {self.max_length} characters." - - # For Base64, we allow larger size, but maybe still impose a limit? - # For now, let's assume max_length applies to text/latex/url - if input_type == InputType.BASE64_IMAGE: - # Heuristic check for base64 validity - if len(content) > 5_000_000: #5MB limit catch - return False, "Image data too large." - # Further base64 validation could be done here if needed - return True, None - - if input_type == InputType.IMAGE_URL: - # Basic URL validation done in detect and OCR, but check again for safety - parsed = urllib.parse.urlparse(content) - # Note: For IMAGE_URL, 'content' here is technically the ACTUALLY EXTRACTED TEXT now if we look at flow above? - # Wait, logic check: - # In `process`: - # 1. detect_type -> IMAGE_URL - # 2. if IMAGE_URL -> ocr -> extracted_text - # 3. cleaned_content = extracted_text - # 4. _validate(cleaned_content, IMAGE_URL) - # So content passed to validate is TEXT. - # BUT `detect_type` is IMAGE_URL. - # So `_validate` logic for IMAGE_URL checking scheme is invalid b/c content is now text. - # We should rely on OCR processor to have validated the image/url itself. - # So here for IMAGE_URL/BASE64_IMAGE, we are validating the EXTRACTED text. - pass - - # if parsed.scheme not in ('http', 'https'): - # return False, "Invalid URL scheme." - # return True, None - - # Check for dangerous payloads in text/latex - for pattern in self._dangerous_patterns: - if pattern.search(content): - return False, "Input contains potentially dangerous content." - - return True, None - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\limiter.py -================================================================================ - -from slowapi import Limiter -from slowapi.util import get_remote_address - -# Initialize Limiter -# We use the client's IP address (get_remote_address) as the default identifier for rate limiting. -limiter = Limiter(key_func=get_remote_address) - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\logging_config.py -================================================================================ - -import logging -import sys -from pythonjsonlogger import jsonlogger - -def configure_logging(): - """ - Configures the root logger to output logs in JSON format. - """ - logger = logging.getLogger() - - # Remove existing handlers to avoid duplication/conflict with Uvicorn default - if logger.handlers: - for handler in logger.handlers: - logger.removeHandler(handler) - - handler = logging.StreamHandler(sys.stdout) - - # Custom format - formatter = jsonlogger.JsonFormatter( - "%(asctime)s %(levelname)s %(name)s %(message)s", - datefmt="%Y-%m-%dT%H:%M:%S%z" - ) - - handler.setFormatter(formatter) - logger.addHandler(handler) - logger.setLevel(logging.INFO) - - # Also silence some noisy libraries if needed - logging.getLogger("uvicorn.access").disabled = True # We might replace this with our own access log if desired - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\math_normalizer.py -================================================================================ - -import re -from typing import Optional, Dict, Any -from dataclasses import dataclass -import logging - -logger = logging.getLogger(__name__) - -@dataclass -class MathIntent: - intent: str - expression: str - variable: Optional[str] = 'x' - original_query: str = "" - -class MathQueryNormalizer: - """ - Normalizes natural language math queries into structured intents - resolvable by symbolic solvers. - """ - - def __init__(self): - self.intent_patterns = { - "derivative": [ - r"derivative of\s+(.+)", - r"derive\s+(.+)", - r"differentiate\s+(.+)", - r"d/dx\s*\[?(.+)\]?" - ], - "integral": [ - r"integral of\s+(.+)", - r"integrate\s+(.+)", - r"antiderivative of\s+(.+)" - ], - "limit": [ - r"limit of\s+(.+)\s+as\s+(\w+)\s+approaches\s+(.+)", - r"lim\s+(.+)" - ], - "equation": [ - r"solve\s+(.+)", - r"find x for\s+(.+)", # basic - r"(.+)=(.+)" # implicit equation if contains = - ], - "arithmetic": [ - r"calculate\s+(.+)", - r"what is\s+(.+)", - r"evaluate\s+(.+)" - ] - } - - # Stop words to clean from expression - self.stop_words = ["what is", "calculate", "the", "please", "solve", "evaluate"] - - def normalize(self, text: str) -> Optional[MathIntent]: - """ - Parses text to identify math intent and extract the core expression. - Returns None if no clear math intent is found. - """ - if not text: - return None - - clean_text = text.lower().strip().rstrip("?") - - # 1. Check specific intents - - # Derivative - for pattern in self.intent_patterns["derivative"]: - match = re.search(pattern, clean_text) - if match: - # Group 1 is usually the expression - raw_expr = match.group(1) - expr = self._clean_expression(raw_expr) - return MathIntent( - intent="derivative", - expression=expr, - variable='x', - original_query=text - ) - - # Integral - for pattern in self.intent_patterns["integral"]: - match = re.search(pattern, clean_text) - if match: - raw_expr = match.group(1) - expr = self._clean_expression(raw_expr) - return MathIntent( - intent="integral", - expression=expr, - variable='x', - original_query=text - ) - - # Equation Solving - if "=" in clean_text: - # Basic heuristic: if it has = it's likely an equation - # Remove "solve", "find x" etc - expr = clean_text - for stop in ["solve", "find x", "calculate", "what is", "evaluate"]: - expr = expr.replace(stop, "") - - return MathIntent( - intent="equation", - expression=expr.strip(), - variable='x', - original_query=text - ) - - # Arithmetic / Simplification - # If it looks like math chars only - if self._is_arithmetic(clean_text): - return MathIntent( - intent="arithmetic", - expression=clean_text, - original_query=text - ) - - return None - - def _clean_expression(self, text: str) -> str: - """Removes common stop words and artifacts.""" - text = text.strip() - - # Remove "what is" if it somehow got in - for stop in self.stop_words: - # Replace start of string - if text.startswith(stop): - text = text[len(stop):].strip() - - return text.strip() - - def _is_arithmetic(self, text: str) -> bool: - """ - Checks if text is primarily arithmetic (numbers, operators). - """ - # Allow basic math chars - allowed_chars = set("0123456789+-*/^().= \t") - - # 1. Must contain at least one digit - if not any(c.isdigit() for c in text): - return False - - # 2. Must only contain allowed chars [and maybe 'x', 'y' for algebra?] - # Let's be strict for "arithmetic" intent, looser for "algebra" if we had it. - # But user query "2x + 5" is algebra/simplification. - - # Let's allow algebraic vars for simplification - allowed_chars.update(set("xyzabc")) - - # Check if characters are valid - for char in text: - if char not in allowed_chars: - return False - - return True - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\ocr.py -================================================================================ - -import base64 -import requests -import io -import logging -from typing import Optional -from PIL import Image, ImageEnhance, ImageOps - -logger = logging.getLogger(__name__) - -class OCRProcessor: - """ - Handles image validation and download. - Note: PaddleOCR has been removed. This class now acts as an image helper. - """ - - def __init__(self, max_size_bytes: int = 5 * 1024 * 1024): # 5MB limit - self.max_size = max_size_bytes - # No OCR engine init needed - - def optimize_base64(self, b64_string: str) -> str: - """ - Optimize base64 image: resize to max 1024px and convert to JPEG. - Returns optimized base64 string. - """ - try: - # Basic strip - if ";base64," in b64_string: - header, data = b64_string.split(";base64,") - else: - header = None - data = b64_string - - img_data = base64.b64decode(data) - img = Image.open(io.BytesIO(img_data)) - - # Resize if too large - max_dim = 1024 - if max(img.size) > max_dim: - img.thumbnail((max_dim, max_dim), Image.Resampling.LANCZOS) - - # Convert to JPEG for compression (if RGBA, convert to RGB) - if img.mode in ('RGBA', 'P'): - img = img.convert('RGB') - - buffer = io.BytesIO() - # Quality 85 is good balance - img.save(buffer, format="JPEG", quality=85) - - return base64.b64encode(buffer.getvalue()).decode('utf-8') - except Exception as e: - logger.warning(f"Image optimization failed, using original: {e}") - return b64_string - - def download_image_as_base64(self, url: str) -> Optional[str]: - """ - Download image from URL and return as base64 string. - """ - try: - response = requests.get(url, timeout=10, stream=True) - response.raise_for_status() - - # Size check - if len(response.content) > self.max_size: - logger.warning(f"Downloaded image bytes {len(response.content)} exceed limit.") - return None - - # Optimize immediately - b64 = base64.b64encode(response.content).decode('utf-8') - return self.optimize_base64(b64) - - except Exception as e: - logger.error(f"Image download failed: {e}") - return None - - def _preprocess_image(self, img: Image.Image) -> Image.Image: - """ - Applies preprocessing to improve image quality for Vision model. - - Grayscale conversion - - Contrast enhancement - - Binarization (Thresholding) - """ - try: - # 1. Convert to grayscale - img = img.convert('L') - - # 2. Enhance contrast - enhancer = ImageEnhance.Contrast(img) - img = enhancer.enhance(2.0) - - # 3. Apply thresholding (binarization) - # This makes the image pure black and white, removing noise - img = img.point(lambda x: 0 if x < 128 else 255, '1') - - return img - except Exception as e: - logger.warning(f"Image preprocessing failed, using original: {e}") - return img - - def _process_image_data(self, image_bytes: bytes) -> Optional[str]: - """ - Validate image format. - Returns dummy string or None. - DEPRECATED: Used to do OCR. Now just validates. - """ - # 1. Size Check - if len(image_bytes) > self.max_size: - logger.warning("Image data exceeds size limit.") - return None - - # 2. Format Validation (using Pillow) - try: - img = Image.open(io.BytesIO(image_bytes)) - img.verify() # Verify it's an image - - # Re-open for processing (verify closes the file) - img = Image.open(io.BytesIO(image_bytes)) - - if img.format.upper() not in ('JPEG', 'JPG', 'PNG', 'BMP', 'WEBP'): - logger.warning(f"Unsupported image format: {img.format}") - return None - - return "VALID_IMAGE" - - except Exception as e: - logger.warning(f"Invalid image file: {e}") - return None - - # Legacy methods stubbed out or removed. - # process_base64 and process_url were used for text extraction. - # Calling them now should return None to indicate no text extracted. - - def process_base64(self, b64_string: str) -> Optional[str]: - return None - - def process_url(self, url: str) -> Optional[str]: - return None - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\orchestrator.py -================================================================================ - - -import logging -import time -from typing import Any, Dict, Optional - -from app.core.input_processor import InputProcessor -from app.memory.cache import CacheManager -from app.memory.database import DatabaseManager -from app.agents.adk_mathminds import MathMindsADKAgent -from app.core.settings import settings - -logger = logging.getLogger(__name__) - -class Orchestrator: - """ - Simplified Orchestrator for MathMinds AI (Pure ADK Architecture). - Delegates all reasoning and tool usage to the MathMindsADKAgent. - """ - - def __init__(self, cache_manager: Optional[CacheManager] = None, db_manager: Optional[DatabaseManager] = None): - try: - self.input_processor = InputProcessor() - self.cache_manager = cache_manager or CacheManager() - self.db_manager = db_manager or DatabaseManager() - - # The Single Source of Truth - self.adk_agent = MathMindsADKAgent() - - except Exception as e: - logger.critical(f"Failed to initialize Orchestrator: {e}") - raise - - async def process_problem(self, text: Optional[str] = None, image: Optional[str] = None, request_id: Optional[str] = None, model_preference: str = "fast", session_id: Optional[str] = None, user_id: Optional[str] = None) -> Dict[str, Any]: - """ - Streamlined Pipeline: Input -> Agent -> Output. - """ - start_time = time.time() - request_id = request_id or "unknown" - - # Default Schema - result_schema = { - "request_id": request_id, - "status": "error", - "source": "google_adk_agent", - "answer": None, - "steps": [], - "explanation": None, - "confidence": 0.0, - "cached": False, - "metadata": { - "latency_ms": 0, - "model": "gemini-flash-adk", - "tools_used": [] - } - } - - try: - # 1. Input Processing - processed = self.input_processor.process_compound(text_input=text, image_input=image) - if not processed.is_valid: - result_schema["explanation"] = processed.error_message - return self._finalize_result(result_schema, start_time) - - # 2. Agent Execution - logger.info("Routing request to ADK Agent") - - # Pass image data if available - image_data_b64 = processed.metadata.get("image_data") - - try: - agent_response = await self.adk_agent.solve( - problem=processed.cleaned_content, - image_data=image_data_b64, - session_id=session_id or "default_session", - user_id=user_id or "default_user" - ) - - result_schema["status"] = "success" - result_schema["answer"] = agent_response - result_schema["explanation"] = "Processed by MathMinds ADK Agent." - result_schema["confidence"] = 1.0 - - except Exception as e: - logger.error(f"ADK Agent execution failed: {e}") - result_schema["explanation"] = f"Agent Error: {str(e)}" - return self._finalize_result(result_schema, start_time) - - # 3. Persistence (Cache & DB) - if result_schema["status"] == "success": - if settings.ENABLE_CACHE: - # Simple hash for caching (content + image) - # Note: In a real agent scenario, caching entire conversations is complex. - # We skip aggressive caching for now to rely on Agent's session memory, - # or we cache only exact single-turn queries if needed. - pass - - # Save to DB for history - self.db_manager.save_problem( - {"content": processed.cleaned_content}, - result_schema - ) - - return self._finalize_result(result_schema, start_time) - - except Exception as e: - logger.error(f"Orchestrator Critical Error: {e}") - result_schema["explanation"] = f"Internal Error: {str(e)}" - return self._finalize_result(result_schema, start_time) - - def _finalize_result(self, schema: Dict, start_time: float) -> Dict: - """Calculates latency and returns final dict.""" - schema["metadata"]["latency_ms"] = int((time.time() - start_time) * 1000) - return schema - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\router.py -================================================================================ - -import enum -import logging - -logger = logging.getLogger(__name__) - -class RouteType(enum.Enum): - WEB = "web" - SYMBOLIC = "symbolic" - GENERATIVE = "generative" # Default Gemini Flow - -class QueryRouter: - """ - Classifies user queries to determine the best execution path. - """ - - def __init__(self): - # Keywords for Web Search - self.web_keywords = [ - "stock", "price", "weather", "news", "current", "latest","today's" - "who is", "when is", "population", "rate","what is" - "http", "https" - ] - - # Keywords for Symbolic Math - self.symbolic_keywords = [ - "integrate", "derivative", "derive", "plot", "solve for", - "simplify", "factor", "limit of", "proof" - ] - - def route(self, query: str) -> RouteType: - """ - Determines the route based on query content. - """ - if not query: - return RouteType.GENERATIVE - - q_lower = query.lower() - - # Check Web - if any(kw in q_lower for kw in self.web_keywords): - logger.info("Routing to WEB") - return RouteType.WEB - - # Check Symbolic - if any(kw in q_lower for kw in self.symbolic_keywords): - logger.info("Routing to SYMBOLIC") - return RouteType.SYMBOLIC - - # Default - logger.info("Routing to GENERATIVE (Default)") - return RouteType.GENERATIVE - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\schemas.py -================================================================================ - -from typing import Any, Dict, Optional, List -from pydantic import BaseModel, Field, model_validator - -class SolveRequest(BaseModel): - """ - Request model for the /solve endpoint. - Supports text-only, image-only, or multimodal (text + image) input. - """ - text: Optional[str] = Field(None, description="The math problem text or specific question about the image.") - image: Optional[str] = Field(None, description="Base64 encoded image string or Image URL.") - session_id: Optional[str] = Field(None, description="Session ID for maintaining chat context.") - model_preference: Optional[str] = Field("fast", description="Model preference: 'fast' or 'reasoning'.") - input: Optional[str] = Field(None, description="Legacy field for backward compatibility.", deprecated=True) - - @property - def effective_text(self) -> Optional[str]: - return self.text or self.input - - @model_validator(mode='before') - @classmethod - def check_input_compatibility(cls, values: Any) -> Any: - # Support legacy 'input' field - if isinstance(values, dict): - if 'input' in values and not values.get('text'): - values['text'] = values['input'] - return values - - @model_validator(mode='after') - def check_at_least_one(self) -> 'SolveRequest': - text = self.text - image = self.image - # We don't check 'input' here because it should have been mapped to 'text' above - if not text and not image: - raise ValueError("At least one of 'text' or 'image' must be provided.") - return self - -class SolveResponse(BaseModel): - """ - Response model for the /solve endpoint. - """ - request_id: str - status: str = Field(..., description="Status of the request (success/error).") - problem_type: str = "unknown" - source: str = "unknown" - answer: Any = Field(None, description="The structured answer from the AI. Can be str, float, or dict.") - steps: List[str] = Field(default_factory=list, description="A list of steps taken to solve the problem.") - explanation: Optional[str] = Field(None, description="A detailed explanation of the solution.") - confidence: float = Field(0.0, description="Confidence score of the answer.") - cached: bool = Field(False, description="Indicates if the response was served from cache.") - error: Optional[str] = Field(None, description="Error message if status is error.") - error_code: Optional[str] = Field(None, description="Error code if status is error.") - metadata: Dict[str, Any] = Field(default_factory=dict, description="Metadata about the processing.") - -class HealthResponse(BaseModel): - """ - Response model for the /health endpoint. - """ - status: str - version: str - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\security.py -================================================================================ - -import logging -import firebase_admin -from firebase_admin import credentials, auth -from fastapi import HTTPException, status, Security -from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials -from app.core.settings import settings - -logger = logging.getLogger(__name__) - -# Initialize Firebase Admin SDK -try: - if settings.FIREBASE_CREDENTIALS_PATH: - cred = credentials.Certificate(settings.FIREBASE_CREDENTIALS_PATH) - firebase_admin.initialize_app(cred) - logger.info("Firebase Admin SDK initialized successfully.") - else: - logger.warning("FIREBASE_CREDENTIALS_PATH not set. Auth will fail if enabled.") -except Exception as e: - logger.error(f"Failed to initialize Firebase: {e}") - -security = HTTPBearer() - -def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)): - """ - Verifies the Firebase ID token. - Returns the decoded token dict if valid. - """ - token = credentials.credentials - - # ----------------------------------------------------- - # MOCK AUTH FOR DEVELOPMENT - # If ENABLE_AUTH is False or token starts with "mock_" AND we are in development - # ----------------------------------------------------- - is_dev = settings.ENV == "development" - if not settings.ENABLE_AUTH or (token.startswith("mock_") and is_dev): - logger.info(f"Using MOCK AUTH for token: {token}") - return {"uid": "dev_user_123", "email": "dev@mathminds.ai"} - - try: - decoded_token = auth.verify_id_token(token) - return decoded_token - except Exception as e: - logger.warning(f"Auth failed: {e}") - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid authentication credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - -def get_current_user(token: dict = Security(verify_token)): - """ - Dependency to get the current user from the token. - """ - return token - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\settings.py -================================================================================ - -from pydantic_settings import BaseSettings -from pydantic import model_validator -from typing import Optional - -class Settings(BaseSettings): - """ - Application Settings validated by Pydantic. - Reads from environment variables and .env file. - """ - # Core API Keys (Required) - GOOGLE_API_KEY: str - GOOGLE_CLOUD_PROJECT: Optional[str] = None - - - # Environment - ENV: str = "development" # development, staging, production - - # Database (Required) - MONGO_URI: Optional[str] = None - MONGO_DB_NAME: str = "mathminds_db" - - # Cache - REDIS_URL: Optional[str] = None - - # API Config - API_HOST: str = "0.0.0.0" - API_PORT: int = 8000 - LOG_LEVEL: str = "INFO" - TIMEOUT_SECONDS: int = 120 - - # Feature Flags - ENABLE_LOCAL_MODELS: bool = True - ENABLE_CACHE: bool = True - ENABLE_AUTH: bool = True - - # Integrations - FIREBASE_CREDENTIALS_PATH: Optional[str] = None - SUPABASE_URL: Optional[str] = None - SUPABASE_KEY: Optional[str] = None - WOLFRAM_APP_ID: Optional[str] = None - - model_config = { - "env_file": ".env", - "case_sensitive": True, - "extra": "ignore" # Ignore extra env vars - } - - @model_validator(mode='after') - def set_defaults_and_validate(self): - # Enforce Production Constraints - if self.ENV == "production": - if not self.MONGO_URI: - raise ValueError("MONGO_URI must be set in production environment") - if not self.REDIS_URL: - raise ValueError("REDIS_URL must be set in production environment") - if not self.FIREBASE_CREDENTIALS_PATH: - # Warning for now, might be critical depending on usage - pass - - # Set Defaults for Development - else: - if not self.MONGO_URI: - self.MONGO_URI = "mongodb://localhost:27017/" - if not self.REDIS_URL: - self.REDIS_URL = "redis://localhost:6379/0" - - return self - -# Singleton instance -settings = Settings() - - -================================================================================ -FILE: e:\madhuri\mathminds\app\core\__init__.py -================================================================================ - - - -================================================================================ -FILE: e:\madhuri\mathminds\app\database\firestore_client.py -================================================================================ - - -import firebase_admin -from firebase_admin import credentials, firestore -import logging -from typing import Dict, Any, Optional, List -from app.core.settings import settings - -logger = logging.getLogger(__name__) - -class FirestoreClient: - """ - Wrapper for Firebase Firestore. - Handles real-time data storage and session management. - """ - - def __init__(self): - self.db = None - self._initialize() - - def _initialize(self): - """ - Initialize Firebase Admin SDK if not already initialized. - """ - try: - # Check if already initialized - if not firebase_admin._apps: - # In production, use default credentials (GOOGLE_APPLICATION_CREDENTIALS) - # Or use service account path from settings - if settings.FIREBASE_CREDENTIALS_PATH: - cred = credentials.Certificate(settings.FIREBASE_CREDENTIALS_PATH) - firebase_admin.initialize_app(cred) - else: - # Use Cloud Run / Default credentials - firebase_admin.initialize_app() - - self.db = firestore.client() - logger.info("Firestore initialized successfully.") - except Exception as e: - logger.error(f"Failed to initialize Firestore: {e}") - self.db = None - - def save_session(self, user_id: str, session_data: Dict[str, Any]): - """ - Save user session data in real-time. - """ - if not self.db: - return - - try: - doc_ref = self.db.collection('sessions').document(user_id) - doc_ref.set(session_data, merge=True) - except Exception as e: - logger.error(f"Error saving session to Firestore: {e}") - - def get_session(self, user_id: str) -> Optional[Dict[str, Any]]: - """ - Retrieve user session. - """ - if not self.db: - return None - - try: - doc = self.db.collection('sessions').document(user_id).get() - if doc.exists: - return doc.to_dict() - return None - except Exception as e: - logger.error(f"Error getting session from Firestore: {e}") - return None - - def add_solution(self, solution_data: Dict[str, Any]): - """ - Add a solved problem to the solutions collection. - """ - if not self.db: - return - - try: - self.db.collection('solutions').add(solution_data) - except Exception as e: - logger.error(f"Error adding solution to Firestore: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\app\memory\cache.py -================================================================================ - -import json -import logging -import os -from typing import Any, Dict, Optional - -import redis -from redis.exceptions import RedisError - -# Configure logging -logger = logging.getLogger(__name__) - -class CacheManager: - """ - Manages Redis cache operations for the AI system. - Handles connections, serialization, and failure scenarios gracefully. - """ - - def __init__(self, redis_url: Optional[str] = None, connection_pool: Optional[redis.ConnectionPool] = None): - """ - Initialize the CacheManager. - - Args: - redis_url: Redis connection string (used if pool not provided). - connection_pool: Existing Redis connection pool. - """ - self.redis_url = redis_url or os.getenv("REDIS_URL", "redis://localhost:6379/0") - self.redis_client = None - - try: - if connection_pool: - self.redis_client = redis.Redis(connection_pool=connection_pool, decode_responses=True) - else: - # If no pool provided, create standard client (which uses internal pool) - # But typically we want to pass the pool. - self.redis_client = redis.from_url(self.redis_url, decode_responses=True) - - # Fast ping to verify connection - self.redis_client.ping() - logger.info(f"Successfully connected to Redis at {self.redis_url}") - - except RedisError as e: - logger.error(f"Failed to connect to Redis: {e}") - self.redis_client = None - - # _connect method is removed/merged into __init__ since we prefer injection - - def get_cached_answer(self, cache_key: str) -> Optional[Dict[str, Any]]: - """ - Retrieve a cached answer by its hash key. - - Args: - cache_key: The unique hash key for the problem. - - Returns: - Optional[Dict[str, Any]]: The cached answer info if found and valid, else None. - """ - if not self.redis_client: - logger.warning("Redis client is not available. Skipping cache lookup.") - return None - - try: - data = self.redis_client.get(cache_key) - if data: - logger.info(f"Cache hit for key: {cache_key}") - return json.loads(data) - logger.debug(f"Cache miss for key: {cache_key}") - return None - except RedisError as e: - logger.error(f"Redis error during get operations: {e}") - return None - except json.JSONDecodeError as e: - logger.error(f"Failed to decode cached data for key {cache_key}: {e}") - return None - - def set_cached_answer(self, cache_key: str, answer: Dict[str, Any], ttl: int = 86400) -> bool: - """ - Cache an answer with a TTL. - - Args: - cache_key: The unique hash key. - answer: The answer data to cache (will be JSON serialized). - ttl: Time-to-live in seconds. Defaults to 86400 (24 hours). - - Returns: - bool: True if successful, False otherwise. - """ - if not self.redis_client: - logger.warning("Redis client is not available. Skipping cache write.") - return False - - try: - serialized_data = json.dumps(answer) - self.redis_client.setex(cache_key, ttl, serialized_data) - logger.info(f"Successfully cached answer for key: {cache_key} with TTL {ttl}") - return True - except (RedisError, TypeError) as e: - # TypeError catches JSON serialization errors - logger.error(f"Failed to cache answer for key {cache_key}: {e}") - return False - - def set_if_not_exists(self, cache_key: str, answer: Dict[str, Any], ttl: int = 86400) -> bool: - """ - Set cache only if key doesn't exist (atomic operation). - Prevents thundering herd when multiple requests populate cache. - - Args: - cache_key: The unique hash key. - answer: The answer data to cache. - ttl: Time-to-live in seconds. - - Returns: - bool: True if set, False if key already existed or error. - """ - if not self.redis_client: - return False - - try: - serialized_data = json.dumps(answer) - # SETNX is atomic - only succeeds if key doesn't exist - # Redis-py set() with nx=True is equivalent to SETNX + EXPIRE - result = self.redis_client.set( - cache_key, - serialized_data, - ex=ttl, - nx=True # Only set if not exists - ) - return bool(result) - except Exception as e: - logger.error(f"Failed to set_if_not_exists for {cache_key}: {e}") - return False - - -================================================================================ -FILE: e:\madhuri\mathminds\app\memory\database.py -================================================================================ - -import logging -import os -from datetime import datetime, timezone -from typing import Any, Dict, Optional, List - -import pymongo -from pymongo import IndexModel, ASCENDING -from pymongo.errors import PyMongoError, ServerSelectionTimeoutError - -# Configure logging -logger = logging.getLogger(__name__) - -class DatabaseManager: - """ - Manages MongoDB operations for the AI system. - Handles persistent storage of solved problems and connection management. - """ - - def __init__(self, mongo_uri: Optional[str] = None, client: Optional[pymongo.MongoClient] = None): - """ - Initialize the DatabaseManager. - - Args: - mongo_uri: MongoDB connection string. - client: Existing PyMongo client (shared pool). - """ - self.mongo_uri = mongo_uri or os.getenv("MONGO_URI", "mongodb://localhost:27017/") - self.client = None - self.db = None - self.collection = None - - try: - if client: - self.client = client - else: - # Create new client with specific pool settings if not provided - self.client = pymongo.MongoClient( - self.mongo_uri, - serverSelectionTimeoutMS=5000, - minPoolSize=1, # Keep at least one connection open - maxPoolSize=50 # Limit max connections - ) - - # Force a call to check if the server is available - self.client.server_info() - - # Setup DB and collection - db_name = "mathminds_ai" - try: - uri_db = pymongo.uri_parser.parse_uri(self.mongo_uri).get('database') - if uri_db: - db_name = uri_db - except Exception: - pass - - self.db = self.client[db_name] - self.collection = self.db["solved_problems"] - - # Ensure index - index = IndexModel([("hash", ASCENDING)], name="hash_index") - self.collection.create_indexes([index]) - - logger.info(f"Successfully connected to MongoDB at {self.mongo_uri} (DB: {db_name})") - - except (PyMongoError, ServerSelectionTimeoutError) as e: - logger.error(f"Failed to connect to MongoDB: {e}") - self.client = None - self.db = None - self.collection = None - - # _connect is merged into __init__ - - def find_by_hash(self, problem_hash: str) -> Optional[Dict[str, Any]]: - """ - Retrieve a solved problem by its hash. - - Args: - problem_hash: The hash generated for the problem text. - - Returns: - Optional[Dict[str, Any]]: The document if found, else None. - """ - if self.collection is None: - logger.warning("MongoDB collection not available. Skipping lookup.") - return None - - try: - doc = self.collection.find_one({"hash": problem_hash}) - return doc - except PyMongoError as e: - logger.error(f"Error finding problem by hash {problem_hash}: {e}") - return None - - def save_problem(self, problem_dict: Dict[str, Any], answer_dict: Dict[str, Any]) -> bool: - """ - Save a solved problem and its answer to the database. - - Args: - problem_dict: Dictionary containing problem details. - Must contain 'hash' if not in answer_dict? - Ideally we assume one of them or we merge them. - The user requested save_problem(problem_dict, answer_dict). - answer_dict: Dictionary containing the answer details. - - Returns: - bool: True if successful, False otherwise. - """ - if self.collection is None: - logger.warning("MongoDB collection not available. Skipping save.") - return False - - try: - # Construct the document - # Expecting 'hash' to be somewhere. If not provided, we can't index it effectively - # for 'find_by_hash'. I will assume it is passed in problem_dict or we generate/extract it. - # But the signature didn't ask for hash arg. - # I will assume problem_dict contains the 'hash' key. - - document = { - "problem": problem_dict, - "answer": answer_dict, - "created_at": datetime.now(timezone.utc), - # Lift hash to top level for easier indexing/querying if present in problem_dict - } - - problem_hash = problem_dict.get("hash") - if problem_hash: - document["hash"] = problem_hash - - result = self.collection.insert_one(document) - logger.info(f"Saved problem with ID: {result.inserted_id}") - return True - except PyMongoError as e: - logger.error(f"Failed to save problem: {e}") - return False - - def create_session(self, session_id: str, title: str = "New Chat") -> bool: - """ - Initialize a new chat session. - """ - if self.db is None: - return False - try: - self.db["chat_sessions"].update_one( - {"session_id": session_id}, - { - "$setOnInsert": { - "session_id": session_id, - "title": title, - "created_at": datetime.now(timezone.utc), - "messages": [] - } - }, - upsert=True - ) - return True - except PyMongoError as e: - logger.error(f"Failed to create session {session_id}: {e}") - return False - - def get_chat_history(self, session_id: str, limit: int = 10) -> List[Dict[str, Any]]: - """ - Retrieve recent messages for a session. - """ - if self.db is None: - return [] - try: - # Get the session document with sliced messages - doc = self.db["chat_sessions"].find_one( - {"session_id": session_id}, - {"messages": {"$slice": -limit}} - ) - if doc and "messages" in doc: - return doc["messages"] - return [] - except PyMongoError as e: - logger.error(f"Failed to get history for {session_id}: {e}") - return [] - - def save_chat_message(self, session_id: str, role: str, content: str) -> bool: - """ - Append a message to the session history. - Also updates the session title if it's the first user message. - """ - if self.db is None: - return False - try: - # logic to update title if it's currently "New Chat" and this is a user message - if role == "user": - session = self.db["chat_sessions"].find_one({"session_id": session_id}) - if session and session.get("title") == "New Chat": - # Generate title from content (truncate) - new_title = content[:50] + "..." if len(content) > 50 else content - self.db["chat_sessions"].update_one( - {"session_id": session_id}, - {"$set": {"title": new_title}} - ) - - # Push the new message - self.db["chat_sessions"].update_one( - {"session_id": session_id}, - { - "$push": { - "messages": { - "role": role, - "content": content, - "timestamp": datetime.now(timezone.utc) - } - } - }, - upsert=True - ) - return True - except PyMongoError as e: - logger.error(f"Failed to save message to {session_id}: {e}") - return False - - # ------------------------------------------------------------------------- - # User Profile Management - # ------------------------------------------------------------------------- - def get_user_profile(self, user_id: str) -> Optional[Dict[str, Any]]: - """ - Retrieve user profile by ID (Firebase UID). - """ - if self.db is None: - return None - try: - return self.db["users"].find_one({"user_id": user_id}) - except PyMongoError as e: - logger.error(f"Failed to get profile for {user_id}: {e}") - return None - - def update_user_profile(self, user_id: str, data: Dict[str, Any]) -> bool: - """ - Update or create user profile. - """ - if self.db is None: - return False - try: - self.db["users"].update_one( - {"user_id": user_id}, - {"$set": {**data, "updated_at": datetime.now(timezone.utc)}}, - upsert=True - ) - return True - except PyMongoError as e: - logger.error(f"Failed to update profile for {user_id}: {e}") - return False - - -================================================================================ -FILE: e:\madhuri\mathminds\app\models\base.py -================================================================================ - -from abc import ABC, abstractmethod -from typing import Dict, Optional, Any - -class BaseModel(ABC): - """ - Abstract base class for all AI models (Gemini, Qwen, etc). - Enforces a consistent interface for the Orchestrator. - """ - - @abstractmethod - async def solve(self, prompt: str, image_data: Optional[str] = None, **kwargs) -> Dict[str, Any]: - """ - Solve a problem using the model. - - Args: - prompt: The text prompt. - image_data: Optional Base64 encoded image or URL. - **kwargs: Additional model-specific arguments. - - Returns: - Dict containing: - - 'answer': The solution text/markdown. - - 'confidence': Float between 0.0 and 1.0. - - 'model': Name of the model used. - - 'metadata': Additional info. - """ - pass - - -================================================================================ -FILE: e:\madhuri\mathminds\app\models\vertex_gemini.py -================================================================================ - - -import logging -from typing import Optional, Dict, Any, List -# vertexai and related imports -# google-cloud-aiplatform is required -try: - import vertexai - from vertexai.generative_models import GenerativeModel, Part - import vertexai.preview.generative_models as generative_models -except ImportError: - vertexai = None - GenerativeModel = None - -from app.core.settings import settings -from app.models.base import BaseModel - -logger = logging.getLogger(__name__) - -class VertexGeminiModel(BaseModel): - """ - Wrapper for Google Vertex AI Gemini API. - Offers enterprise features, higher quotas, and better monitoring than the standard API. - """ - - def __init__(self, project_id: str = None, location: str = "us-central1", model_name: str = "gemini-2.5-flash"): - """ - Initialize Vertex AI client. - """ - if not vertexai: - logger.error("google-cloud-aiplatform not installed. VertexGeminiModel disabled.") - self.model = None - return - - self.project_id = project_id or settings.GOOGLE_CLOUD_PROJECT - self.location = location - self.model_name = model_name - - if not self.project_id: - logger.warning("No Google Cloud Project ID provided. Vertex AI calls may fail.") - - try: - vertexai.init(project=self.project_id, location=self.location) - self.model = GenerativeModel(self.model_name) - except Exception as e: - logger.error(f"Failed to initialize Vertex AI: {e}") - self.model = None - - async def solve(self, prompt: str, image_data: Optional[str] = None, **kwargs) -> Dict[str, Any]: - """ - Solve problem using Vertex AI. - """ - if not self.model: - return {"error": "Vertex AI not initialized"} - - full_prompt = f""" - You are an expert math solver using Vertex AI. - Return strictly valid JSON. - - Strategy: "Think Aloud -> Solve -> Verify" - - Format: - {{ - "latex": "Problem in LaTeX", - "reasoning": "Step-by-step solution", - "final_answer": "Boxed answer", - "confidence_score": 0.0-1.0 - }} - - Problem: {prompt} - """ - - generation_config = { - "max_output_tokens": 2048, - "temperature": 0.2, - "top_p": 0.95, - } - - # Safety settings to block minimal content - safety_settings = { - generative_models.HarmCategory.HARM_CATEGORY_HATE_SPEECH: generative_models.HarmBlockThreshold.BLOCK_ONLY_HIGH, - generative_models.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: generative_models.HarmBlockThreshold.BLOCK_ONLY_HIGH, - generative_models.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: generative_models.HarmBlockThreshold.BLOCK_ONLY_HIGH, - generative_models.HarmCategory.HARM_CATEGORY_HARASSMENT: generative_models.HarmBlockThreshold.BLOCK_ONLY_HIGH, - } - - try: - # TODO: Handle image_data (convert base64 to Part) - # For now, text only - if image_data: - logger.warning("Image data not fully supported in Vertex wrapper yet.") - - responses = await self.model.generate_content_async( - [full_prompt], - generation_config=generation_config, - safety_settings=safety_settings, - stream=False, - ) - - # Vertex AI response parsing - text_response = responses.text - - # Simple wrapper return - in production, need parsing logic similar to GeminiModel - return { - "raw_response": text_response, - "model": "vertex-gemini", - "status": "success" - } - - except Exception as e: - logger.error(f"Vertex AI generation failed: {e}") - return {"error": str(e)} - - -================================================================================ -FILE: e:\madhuri\mathminds\app\reasoning\classifier.py -================================================================================ - -import logging -import os -import json -import re -from typing import Dict, Any, Optional - -from google import genai -from google.genai import types -import pybreaker -from tenacity import retry, stop_after_attempt, wait_random_exponential, retry_if_exception_type - -logger = logging.getLogger(__name__) - -class QueryClassifier: - """ - Classifies user queries to determine if they require web options - and what specific information to extract. - """ - - def __init__(self, api_key: Optional[str] = None, model_name: str = "gemini-2.5-flash"): - self.api_key = api_key or os.getenv("GOOGLE_API_KEY") - if not self.api_key: - logger.warning("No API key provided for QueryClassifier.") - - self.client = genai.Client(api_key=self.api_key) - self.model_name = model_name - - # Robustness - self.breaker = pybreaker.CircuitBreaker(fail_max=5, reset_timeout=60) - - @retry(stop=stop_after_attempt(3), wait=wait_random_exponential(multiplier=1, max=60)) - def classify(self, query: str) -> Dict[str, Any]: - """ - Classifies the query. - """ - return self.breaker.call(self._classify_internal, query) - - def _classify_internal(self, query: str) -> Dict[str, Any]: - prompt = f""" - Analyze the following user query to determine if it requires external information (web search, live data, specific facts) or if it can be answered by a standard math/logic solver. - - If it requires web search, identify the specific 'extraction_focus' (the exact value or fact needed, e.g., 'stock price', 'release date', 'population'). - - Query: "{query}" - - Output JSON format: - {{ - "requires_web_search": boolean, - "search_queries": ["list of optimal search queries"], - "extraction_focus": "keyword or phrase to look for in the page content to find the answer", - "intent": "general_info" | "specific_value" | "date_lookup" - }} - """ - - try: - response = self.client.models.generate_content( - model=self.model_name, - contents=[prompt], - config=types.GenerateContentConfig( - response_mime_type="application/json", - temperature=0.0 - ) - ) - - if not response.text: - return {"requires_web_search": False} - - return json.loads(response.text) - except Exception as e: - logger.error(f"Classification failed: {e}") - # Fail safe to no search - return {"requires_web_search": False} - - -================================================================================ -FILE: e:\madhuri\mathminds\app\tools\advanced_ocr.py -================================================================================ - -import logging -import torch -from transformers import TrOCRProcessor, VisionEncoderDecoderModel -from PIL import Image -import io - -logger = logging.getLogger(__name__) - -class AdvancedOCR: - """ - Advanced OCR using Hugging Face Transformers (TrOCR). - Specialized for handwritten text and mathematical expressions. - """ - - def __init__(self, model_name: str = "microsoft/trocr-base-handwritten"): - """ - Initialize the TrOCR model and processor. - """ - self.model_name = model_name - self.processor = None - self.model = None - - # Lazy loading to avoid heavy startup time if not needed immediately - self._loaded = False - - def load_model(self): - """ - Load the model into memory. - """ - if self._loaded: - return - - try: - logger.info(f"Loading TrOCR model: {self.model_name}...") - self.processor = TrOCRProcessor.from_pretrained(self.model_name) - self.model = VisionEncoderDecoderModel.from_pretrained(self.model_name) - - # Move to GPU if available - self.device = "cuda" if torch.cuda.is_available() else "cpu" - self.model.to(self.device) - - self._loaded = True - logger.info("TrOCR model loaded successfully.") - except Exception as e: - logger.error(f"Failed to load TrOCR model: {e}") - self._loaded = False - raise e - - def extract_handwriting(self, image: Image.Image) -> str: - """ - Extract handwritten text from a PIL Image. - """ - if not self._loaded: - self.load_model() - - try: - # Prepare image - if image.mode != "RGB": - image = image.convert("RGB") - - pixel_values = self.processor(images=image, return_tensors="pt").pixel_values - pixel_values = pixel_values.to(self.device) - - # Generate text - generated_ids = self.model.generate(pixel_values) - generated_text = self.processor.batch_decode(generated_ids, skip_special_tokens=True)[0] - - return generated_text - - except Exception as e: - logger.error(f"TrOCR extraction failed: {e}") - return "" - - def process_image_bytes(self, image_bytes: bytes) -> str: - """ - Process raw image bytes. - """ - try: - image = Image.open(io.BytesIO(image_bytes)) - return self.extract_handwriting(image) - except Exception as e: - logger.error(f"Error processing image bytes: {e}") - return "" - - -================================================================================ -FILE: e:\madhuri\mathminds\app\tools\data_processor.py -================================================================================ - -import pandas as pd -import numpy as np -import logging -from typing import Dict, Any, List, Optional - -logger = logging.getLogger(__name__) - -class DataProcessor: - """ - Handles data processing and statistical analysis. - Uses NumPy and Pandas for robust calculations. - """ - - def __init__(self): - pass - - def calculate_stats(self, data: List[float]) -> Dict[str, float]: - """ - Calculate basic statistical metrics for a list of numbers. - """ - if not data: - return {} - - try: - arr = np.array(data) - stats = { - "mean": float(np.mean(arr)), - "median": float(np.median(arr)), - "std_dev": float(np.std(arr)), - "min": float(np.min(arr)), - "max": float(np.max(arr)), - "variance": float(np.var(arr)) - } - return stats - except Exception as e: - logger.error(f"Error calculating stats: {e}") - return {"error": str(e)} - - def analyze_timeseries(self, dates: List[Any], values: List[float]) -> Dict[str, Any]: - """ - Analyze time series data using Pandas. - """ - if not dates or not values or len(dates) != len(values): - return {"error": "Invalid input data"} - - try: - df = pd.DataFrame({"date": dates, "value": values}) - # parse dates if strings - if isinstance(dates[0], str): - df["date"] = pd.to_datetime(df["date"]) - - df.set_index("date", inplace=True) - - # Calculate rolling metrics - df["rolling_mean_7d"] = df["value"].rolling(window=7, min_periods=1).mean() - - # Trend (simple linear regression logic via numpy polyfit) - # x as numeric timestamps - x = np.arange(len(df)) - y = df["value"].values - slope, intercept = np.polyfit(x, y, 1) - - trend = "stable" - if slope > 0.05: trend = "increasing" - elif slope < -0.05: trend = "decreasing" - - return { - "trend": trend, - "slope": float(slope), - "latest_value": float(df["value"].iloc[-1]), - "rolling_mean_last": float(df["rolling_mean_7d"].iloc[-1]) - } - - except Exception as e: - logger.error(f"Error analyzing timeseries: {e}") - return {"error": str(e)} - - -================================================================================ -FILE: e:\madhuri\mathminds\app\tools\selenium_scraper.py -================================================================================ - - -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from selenium.webdriver.chrome.service import Service -from webdriver_manager.chrome import ChromeDriverManager -import logging - -logger = logging.getLogger(__name__) - -class SeleniumScraper: - """ - Fallback scraper using Selenium for sites where Playwright fails. - """ - - def __init__(self, headless: bool = True): - self.headless = headless - self.driver = None - - def _setup_driver(self): - """ - Initialize Chrome Driver. - """ - try: - options = Options() - if self.headless: - options.add_argument('--headless') - options.add_argument('--no-sandbox') - options.add_argument('--disable-dev-shm-usage') - - # Auto-install driver - service = Service(ChromeDriverManager().install()) - self.driver = webdriver.Chrome(service=service, options=options) - except Exception as e: - logger.error(f"Failed to setup Selenium driver: {e}") - raise e - - def scrape(self, url: str) -> str: - """ - Scrape a URL and return page source. - """ - if not self.driver: - self._setup_driver() - - try: - logger.info(f"Selenium scraping: {url}") - self.driver.get(url) - # Add explicit waits if needed - return self.driver.page_source - except Exception as e: - logger.error(f"Selenium scrape failed: {e}") - return "" - finally: - # For simplistic usage, we might close after each scrape or keep open. - # Here we close to save resources as it's a fallback. - if self.driver: - self.driver.quit() - self.driver = None - - -================================================================================ -FILE: e:\madhuri\mathminds\app\tools\similarity_search.py -================================================================================ - -import logging -from typing import List, Dict, Any, Optional -from supabase import create_client, Client -from google import genai -from google.genai import types -from app.core.settings import settings - -logger = logging.getLogger(__name__) - -class SimilarProblemFinder: - """ - Tool to find similar math problems using Vector Search (Supabase). - """ - - def __init__(self): - self.supabase: Optional[Client] = None - self.gemini_client = None - - if settings.SUPABASE_URL and settings.SUPABASE_KEY: - try: - self.supabase = create_client(settings.SUPABASE_URL, settings.SUPABASE_KEY) - logger.info("Supabase client initialized.") - except Exception as e: - logger.error(f"Failed to init Supabase: {e}") - else: - logger.warning("Supabase URL/Key missing. Vector search disabled.") - - if settings.GOOGLE_API_KEY: - self.gemini_client = genai.Client(api_key=settings.GOOGLE_API_KEY) - - def search(self, query_text: str, limit: int = 3) -> List[Dict[str, Any]]: - """ - Embeds the query and searches the 'math_problems' table in Supabase. - """ - if not self.supabase or not self.gemini_client: - return [] - - try: - # 1. Generate Embedding - embedding_resp = self.gemini_client.models.embed_content( - model="models/gemini-embedding-001", - contents=query_text, - config=types.EmbedContentConfig(output_dimensionality=768) - ) - embedding = embedding_resp.embeddings[0].values - - # 2. RPC call to Supabase (assuming 'match_problems' function acts on 'math_problems' table) - # We assume a Postgres function match_problems(query_embedding vector, match_threshold float, match_count int) - response = self.supabase.rpc( - "match_problems", - { - "query_embedding": embedding, - "match_threshold": 0.7, - "match_count": limit - } - ).execute() - - return response.data - - except Exception as e: - logger.error(f"Vector search failed: {e}") - return [] - - def index_problem(self, problem_text: str, solution_text: str, metadata: Dict[str, Any]): - """ - Saves a solved problem and its embedding to Supabase. - """ - if not self.supabase or not self.gemini_client: - return - - try: - # 1. Embed - embedding_resp = self.gemini_client.models.embed_content( - model="models/gemini-embedding-001", - contents=problem_text, - config=types.EmbedContentConfig(output_dimensionality=768) - ) - embedding = embedding_resp.embeddings[0].values - - # 2. Insert - data = { - "problem_text": problem_text, - "solution_text": solution_text, - "embedding": embedding - } - - self.supabase.table("math_problems").insert(data).execute() - logger.info("Indexed problem in Vector DB.") - - except Exception as e: - logger.error(f"Failed to index problem: {e}") - # Do not raise, just log. - pass - - -================================================================================ -FILE: e:\madhuri\mathminds\app\tools\symbolic_solver.py -================================================================================ - -import logging -import os -from typing import Dict, Any, Optional, Union -import sympy -from sympy.parsing.sympy_parser import parse_expr -from app.core.math_normalizer import MathIntent -from app.core.settings import settings - -# Try importing wolframalpha, handling if not installed or configured -try: - import wolframalpha -except ImportError: - wolframalpha = None - -logger = logging.getLogger(__name__) - -class SymbolicSolver: - """ - Tool for solving math problems symbolically. - Prioritizes WolframAlpha (if AppID present), falls back to SymPy. - """ - - def __init__(self, wolfram_app_id: Optional[str] = None): - self.wolfram_app_id = wolfram_app_id or settings.WOLFRAM_APP_ID - self.wolfram_client = None - - logger.info(f"Initializing SymbolicSolver. WolframAppID present: {bool(self.wolfram_app_id)}") - - if self.wolfram_app_id and wolframalpha: - try: - logger.info("Attempting to create WolframAlpha client...") - self.wolfram_client = wolframalpha.Client(self.wolfram_app_id) - logger.info("WolframAlpha client created.") - except Exception as e: - logger.warning(f"Failed to initialize WolframAlpha client: {e}") - - def solve(self, query: Union[str, MathIntent]) -> Dict[str, Any]: - """ - Attempts to solve the query symbolically. - Accepts either a raw string (tried via Wolfram) or a structured MathIntent (for SymPy). - """ - # Unwrap intent if passed - intent = None - raw_query = query - if isinstance(query, MathIntent): - intent = query - raw_query = intent.original_query or intent.expression - - logger.info(f"SymbolicSolver triggered for query: {raw_query}") - - # 1. Try WolframAlpha (best for natural language or complex stuff) - if self.wolfram_client: - try: - # Wolfram prefers natural language usually - res = self.wolfram_client.query(raw_query) - answer_text = "" - for pod in res.pods: - for sub in pod.subpods: - if sub.plaintext: - answer_text += f"{pod.title}: {sub.plaintext}\n" - - if answer_text: - return { - "source": "wolfram_alpha", - "content": answer_text, - "status": "success" - } - - except Exception as e: - logger.warning(f"WolframAlpha query failed: {e}") - - # 2. Try SymPy (Local Fallback) - # We need a structured intent for SymPy to work reliably. - # If we just got a string and Wolfram failed, we can't easily use SymPy - # unless it was already normalized. - - if not intent: - return { - "source": "symbolic_solver", - "error": "WolframAlpha failed and no structured MathIntent provided for SymPy.", - "status": "error" - } - - try: - # Pre-processing for SymPy syntax - # handle power operator ^ -> ** - expr_str = intent.expression.replace("^", "**") - - # handle implicit multiplication (simple regex) - import re - expr_str = re.sub(r'(\d)([a-z])', r'\1*\2', expr_str) - expr_str = re.sub(r'\)\(', ')*(', expr_str) - - target_var = sympy.symbols(intent.variable or 'x') - result_latex = "" - - if intent.intent == "derivative": - expr = parse_expr(expr_str) - res = sympy.diff(expr, target_var) - result_latex = sympy.latex(res) - - elif intent.intent == "integral": - expr = parse_expr(expr_str) - res = sympy.integrate(expr, target_var) - result_latex = sympy.latex(res) - - elif intent.intent == "equation": - # Expecting "lhs = rhs" or just expression assumed = 0 - parts = expr_str.split("=") - if len(parts) == 2: - lhs = parse_expr(parts[0]) - rhs = parse_expr(parts[1]) - solution = sympy.solve(lhs - rhs, target_var) - else: - # Assume expr = 0 - expr = parse_expr(expr_str) - solution = sympy.solve(expr, target_var) - - result_latex = sympy.latex(solution) - - elif intent.intent == "limit": - # TODO: Parsing limits needs 'approaches' value, logic not fully here yet - # Fallback implementation - return {"source": "symbolic_solver", "status": "error", "error": "Limit parsing not fully implemented"} - - elif intent.intent == "arithmetic" or intent.intent == "simplification": - expr = parse_expr(expr_str) - res = sympy.simplify(expr) - result_latex = sympy.latex(res) - - else: - return {"source": "symbolic_solver", "status": "error", "error": f"Unknown intent: {intent.intent}"} - - return { - "source": "sympy_local", - "content": result_latex, - "status": "success" - } - - except Exception as e: - logger.warning(f"SymPy execution failed: {e}") - return { - "source": "symbolic_solver", - "error": str(e), - "status": "error" - } - - -================================================================================ -FILE: e:\madhuri\mathminds\app\tools\vision_analyzer.py -================================================================================ - -import logging -import base64 -import numpy as np -import cv2 -from typing import Dict, Any, Optional - -logger = logging.getLogger(__name__) - -class VisionAnalyzer: - """ - Applies mathematical reasoning to visual inputs using YOLO (Quantitative) - and prepares context for Gemini (Qualitative). - """ - - def __init__(self, model_path: str = "yolov8n.pt"): - """ - Initialize YOLO model. - Args: - model_path: Path to YOLO weights. Defaults to nano model (will download if missing). - """ - try: - logger.info(f"Loading YOLO model: {model_path}") - # Lazy import to avoid startup lag - from ultralytics import YOLO - self.model = YOLO(model_path) - - # Color ranges in HSV - # Note: OpenCV uses H: 0-179, S: 0-255, V: 0-255 - self.color_ranges = { - "red1": ((0, 70, 50), (10, 255, 255)), - "red2": ((170, 70, 50), (180, 255, 255)), - "green": ((36, 70, 50), (89, 255, 255)), - "blue": ((90, 70, 50), (128, 255, 255)), - "yellow": ((20, 100, 100), (35, 255, 255)), - "black": ((0, 0, 0), (180, 255, 30)), - "white": ((0, 0, 200), (180, 50, 255)), - "gray": ((0, 0, 50), (180, 50, 200)) - } - - except Exception as e: - logger.error(f"Failed to load YOLO model: {e}") - self.model = None - - # Define aliases for Semantic Grounding - ALIAS_MAP = { - "sports ball": ["ball", "marble", "sphere", "globe", "orb"], - "bottle": ["flask", "container", "vial"], - "cup": ["mug", "glass", "tumbler"], - "book": ["notebook", "textbook", "novel"], - "vase": ["urn", "pot", "jar"] # Added vase for the marble misclassification - } - - def get_canonical_name(self, detected_name: str, user_query: str) -> str: - """ - Checks if the user's query mentions an alias for a detected object. - """ - user_query_lower = user_query.lower() - - # If the detected name is in the query, keep it - if detected_name in user_query_lower: - return detected_name - - # Check aliases - for canonical, aliases in self.ALIAS_MAP.items(): - if detected_name == canonical: - for alias in aliases: - if alias in user_query_lower: - return alias # Return the word the user actually used - - return detected_name - - def _detect_color(self, roi: np.ndarray) -> str: - """ - Detect dominant color in a Region of Interest (ROI). - """ - if roi.size == 0: - return "unknown" - - hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) - max_pixels = 0 - dominant_color = "unknown" - - for color, (lower, upper) in self.color_ranges.items(): - mask = cv2.inRange(hsv_roi, np.array(lower), np.array(upper)) - count = cv2.countNonZero(mask) - - # Merit handling for split red ranges - actual_color = "red" if "red" in color else color - - # Store counts? Simple max strategy for now - if count > max_pixels: - max_pixels = count - dominant_color = actual_color - - return dominant_color - - def analyze(self, image_data: str, query: str = "") -> Dict[str, Any]: - """ - Analyze the image. - - Args: - image_data: Base64 encoded image string. - query: User's query to determine intent (optional). - - Returns: - Dict containing structured analysis results. - """ - result = { - "vision_mode": "qualitative", # Default to Gemini unless YOLO runs - "quantitative_analysis": None, - "status": "success" - } - - if not self.model: - return {"status": "error", "error": "Model not initialized"} - - # Heuristic: Only run YOLO if query implies counting/detection/quantification - keywords = ["count", "how many", "number of", "calculate", "probability", "statistics", "quantify", "fraction", "percentage"] - should_run_yolo = any(k in query.lower() for k in keywords) if query else True - - if not should_run_yolo: - return result - - try: - # Decode image - img_bytes = base64.b64decode(image_data) - nparr = np.frombuffer(img_bytes, np.uint8) - img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) - - if img is None: - raise ValueError("Failed to decode image") - - # Run Interface - results = self.model(img, verbose=False) - - detections = {} - confidences = [] - - for r in results: - for box in r.boxes: - # Confidence - conf = float(box.conf[0]) - confidences.append(conf) - - # Class Name - cls_id = int(box.cls[0]) - raw_class_name = self.model.names[cls_id] - class_name = self.get_canonical_name(raw_class_name, query) - - # Color Detection - x1, y1, x2, y2 = map(int, box.xyxy[0]) - # Clamp coordinates - h, w, _ = img.shape - x1, y1 = max(0, x1), max(0, y1) - x2, y2 = min(w, x2), min(h, y2) - - roi = img[y1:y2, x1:x2] - color = self._detect_color(roi) - - # Key construction: e.g. "red_sports ball" - key = f"{color}_{class_name}" - - if key in detections: - detections[key] += 1 - else: - detections[key] = 1 - - if detections: - total_objects = sum(detections.values()) - avg_conf = sum(confidences) / len(confidences) if confidences else 0.0 - - result["vision_mode"] = "quantitative" - result["quantitative_analysis"] = { - "objects": detections, - "total_objects": total_objects, - "avg_confidence": round(avg_conf, 2) - } - else: - # YOLO ran but found nothing - result["vision_mode"] = "quantitative" # It still ran - result["quantitative_analysis"] = { - "objects": {}, - "total_objects": 0, - "avg_confidence": 0.0 - } - - except Exception as e: - logger.error(f"Vision Analysis failed: {e}") - result["status"] = "error" - result["error"] = str(e) - - return result - - -================================================================================ -FILE: e:\madhuri\mathminds\app\tools\web_scraper.py -================================================================================ - -import logging -import asyncio -from typing import Optional, Dict, Any -from playwright.sync_api import sync_playwright -import functools -from bs4 import BeautifulSoup -from fake_useragent import UserAgent -from concurrent.futures import ProcessPoolExecutor - -logger = logging.getLogger(__name__) - -def run_playwright_sync(query: str, headless: bool, extraction_focus: Optional[str] = None) -> Dict[str, Any]: - """ - Scrapes a webpage associated with the query using Playwright with anti-detection measures. - Uses BeautifulSoup for robust text extraction. - """ - ua = UserAgent() - user_agent = ua.random - - try: - with sync_playwright() as p: - browser = p.chromium.launch(headless=headless) - context = browser.new_context( - user_agent=user_agent, - viewport={"width": 1280, "height": 720} - ) - page = context.new_page() - - # Determine URL Logic - q_lower = query.lower() - if query.startswith("http"): - search_url = query - elif "gold" in q_lower and ("rate" in q_lower or "price" in q_lower): - search_url = "https://www.goodreturns.in/gold-rates/" - elif "weather" in q_lower: - # Clean location logic - location = q_lower.replace("weather", "").replace(" in ", "").replace(" at ", "").strip() - search_url = f"https://wttr.in/{location}?format=3" if location else "https://wttr.in/?format=3" - elif "stock" in q_lower or "share" in q_lower: - search_url = f"https://finance.yahoo.com/lookup?s={query}" - else: - search_url = f"https://html.duckduckgo.com/html/?q={query}" - - logger.info(f"Scraping: {search_url} | UA: {user_agent}") - - try: - page.goto(search_url, timeout=30000, wait_until="domcontentloaded") - except Exception as e: - logger.warning(f"Primary nav failed: {e}. Fallback to DDG.") - search_url = f"https://html.duckduckgo.com/html/?q={query}" - page.goto(search_url, timeout=30000, wait_until="domcontentloaded") - - # Content Extraction via BeautifulSoup - html_content = page.content() - soup = BeautifulSoup(html_content, "html.parser") - - # Remove junk elements - for script in soup(["script", "style", "nav", "footer", "header", "noscript", "svg"]): - script.decompose() - - # Get clean text - text = soup.get_text(separator="\n", strip=True) - - final_content = "" - if extraction_focus: - # Basic targeted extraction (could be improved with regex or embedding search) - lines = text.split("\n") - relevant_lines = [line for line in lines if extraction_focus.lower() in line.lower()] - if relevant_lines: - final_content = "\n".join(relevant_lines[:20]) # Limit relevant lines - else: - final_content = text[:2000] + "\n[Note: Focus term not found]" - else: - final_content = text[:5000] - - browser.close() - - return { - "source": "web_scraper", - "url": search_url, - "content": final_content, - "status": "success" - } - - except Exception as e: - logger.error(f"Scraping error: {e}") - return { - "source": "web_scraper", - "error": str(e), - "status": "error" - } - -class WebScraper: - """ - Tool for fetching live data from websites using Playwright. - Useful for queries requiring real-time context (e.g., stock prices, weather, news). - """ - - def __init__(self, headless: bool = True): - self.headless = headless - # We use a ProcessPoolExecutor to run Playwright in a separate process. - # This is CRITICAL on Windows if the main process uses SelectorEventLoopPolicy, - # as Playwright requires ProactorEventLoopPolicy. - self.executor = ProcessPoolExecutor(max_workers=1) - - async def scrape(self, query: str, extraction_focus: Optional[str] = None) -> Dict[str, Any]: - """ - Scrapes data relevant to the query. - runs the scraping logic in a separate process. - - Args: - query: The search query or URL. - extraction_focus: Optional keyword to focus extraction on. - """ - logger.info(f"WebScraper triggered for query: {query}, focus: {extraction_focus}") - - loop = asyncio.get_running_loop() - - # Run in separate process - try: - result = await loop.run_in_executor( - self.executor, - functools.partial(run_playwright_sync, query, self.headless, extraction_focus) - ) - return result - except Exception as e: - logger.error(f"Process execution failed: {e}") - return { - "source": "web_scraper", - "error": f"Process execution failed: {str(e)}", - "status": "error" - } - - -================================================================================ -FILE: e:\madhuri\mathminds\app\tools\__init__.py -================================================================================ - - - -================================================================================ -FILE: e:\madhuri\mathminds\app\utils\hashing.py -================================================================================ - -import hashlib -import unicodedata - -def generate_problem_hash(text: str, image_data: str = None) -> str: - """ - Generates a deterministic SHA256 hash for a given problem text AND optional image. - """ - if not text and not image_data: - raise ValueError("Input text and image cannot both be empty for hashing.") - - # Normalize unicode characters - normalized_text = unicodedata.normalize('NFKC', text or "") - - # Lowercase and strip whitespace - cleaned_text = normalized_text.lower().strip() - - # Base content - content_to_hash = cleaned_text - - # Append image data if present - if image_data: - # Image data is usually a long base64 string. - # We append it to ensure uniqueness for visual problems. - content_to_hash += f"|image:{image_data}" - - # Encode to bytes - encoded_text = content_to_hash.encode('utf-8') - - # Generate SHA256 hash - return hashlib.sha256(encoded_text).hexdigest() - - -================================================================================ -FILE: e:\madhuri\mathminds\app\validation\answer_checker.py -================================================================================ - -from typing import Any, Dict, List, Optional, Tuple - -class AnswerValidator: - """ - Validates AI-generated answers against quality and safety standards. - """ - - def __init__(self, confidence_threshold: float = 0.5): - """ - Initialize the AnswerValidator. - - Args: - confidence_threshold: Minimum confidence score required. Defaults to 0.5. - """ - self.confidence_threshold = confidence_threshold - self.required_fields = ["latex", "reasoning", "final_answer", "confidence_score"] - - def validate(self, response: Dict[str, Any], is_math_problem: bool = True) -> Tuple[bool, List[str]]: - """ - Validates the AI response. - - Args: - response: The JSON response dictionary from the AI. - is_math_problem: Whether the input was identified as a math problem. - If True, checks for LaTeX content. - - Returns: - Tuple[bool, List[str]]: (IsValid, List of error reasons) - """ - errors = [] - - # 1. Check required fields - for field in self.required_fields: - if field not in response: - errors.append(f"Missing required field: {field}") - - if errors: - return False, errors - - # 2. check for hallucinated/empty content - # sometimes models succeed but return empty strings - if not response.get("final_answer") or str(response.get("final_answer")).strip() == "": - errors.append("Final answer is empty.") - - if not response.get("reasoning") or str(response.get("reasoning")).strip() == "": - errors.append("Reasoning is empty.") - - # 3. Verify LaTeX presence for math problems - # We assume 'latex' field should contain some latex-like distinct characters if it's a math problem - # or at least not be empty. - if is_math_problem: - latex_content = response.get("latex", "") - if not latex_content or str(latex_content).strip() == "": - errors.append("LaTeX content is missing for a math problem.") - # Optional: heuristic check for common latex symbols if we want to be stricter - # if "\\" not in latex_content and "$" not in latex_content: - # errors.append("LaTeX content does not appear to contain valid LaTeX syntax.") - - # 4. Confidence threshold check - try: - score = float(response.get("confidence_score", 0.0)) - if score < self.confidence_threshold: - errors.append(f"Confidence score {score} is below threshold {self.confidence_threshold}.") - except (ValueError, TypeError): - errors.append("Invalid confidence score format.") - - return len(errors) == 0, errors - - -================================================================================ -FILE: e:\madhuri\mathminds\backend\cache\redis_cache.py -================================================================================ - -import os -import json -import logging -from typing import Optional, Any, Union -import redis -from redis.exceptions import RedisError - -# Configure logging -logger = logging.getLogger(__name__) - -class RedisCache: - def __init__(self, redis_url: Optional[str] = None): - """ - Initialize the RedisCache wrapper. - - Args: - redis_url: Connection URL for Redis. Defaults to REDIS_URL env var or localhost. - """ - self.redis_url = redis_url or os.getenv("REDIS_URL", "redis://localhost:6379/0") - self.client = None - self._connect() - - def _connect(self): - """Establish Redis connection.""" - try: - self.client = redis.from_url(self.redis_url, decode_responses=True) - self.client.ping() - logger.info("Connected to Redis cache.") - except RedisError as e: - logger.error(f"Failed to connect to Redis: {e}") - self.client = None - - def get(self, problem_hash: str) -> Optional[Any]: - """ - Retrieve result from cache using problem_hash. - - Args: - problem_hash: The unique hash of the problem. - - Returns: - The cached result (deserialized) or None if not found/error. - """ - if not self.client: - return None - - try: - data = self.client.get(problem_hash) - if data: - return json.loads(data) - return None - except (RedisError, json.JSONDecodeError) as e: - logger.error(f"Error retrieving from cache: {e}") - return None - - def set(self, problem_hash: str, result: Any, ttl: int = 86400) -> bool: - """ - Store result in cache with TTL. - - Args: - problem_hash: The unique hash key. - result: The data to cache (will be JSON serialized). - ttl: Time-to-live in seconds (default 1 day). - - Returns: - True if successful, False otherwise. - """ - if not self.client: - return False - - try: - serialized = json.dumps(result) - return bool(self.client.setex(problem_hash, ttl, serialized)) - except (RedisError, TypeError) as e: - logger.error(f"Error setting cache: {e}") - return False - - -================================================================================ -FILE: e:\madhuri\mathminds\frontend\app.py -================================================================================ - -import streamlit as st -import requests -import json -import base64 -from PIL import Image -import io -import os -import uuid -import time -from streamlit_drawable_canvas import st_canvas -from dotenv import load_dotenv - -# Load env immediately -load_dotenv() - -# ==================================================== -# Page Config -# ==================================================== -st.set_page_config( - page_title="MathMinds AI", - page_icon="🧠", - layout="wide", - initial_sidebar_state="expanded" -) - -# ==================================================== -# Premium Global Styling (Glassmorphism + Typography) -# ==================================================== -st.markdown(""" - -""", unsafe_allow_html=True) - -# ==================================================== -# Config & State -# ==================================================== -API_URL = "http://localhost:8000/solve" # Ensure this matches implementation -HISTORY_FILE = "chat_history.json" - -if "chat_sessions" not in st.session_state: - if os.path.exists(HISTORY_FILE): - with open(HISTORY_FILE, "r") as f: - st.session_state.chat_sessions = json.load(f) - else: - st.session_state.chat_sessions = {} - -if "active_session_id" not in st.session_state: - sid = str(uuid.uuid4()) - st.session_state.chat_sessions[sid] = {"title": "New Session", "messages": [], "created_at": time.time()} - st.session_state.active_session_id = sid - -# Auth State (Mocked for UI demo, replace with real Firebase token logic) -if "user" not in st.session_state: - st.session_state.user = None # {"email": "demo@user.com", "token": "mock_token"} - -# ==================================================== -# Helper Functions -# ==================================================== -def save_history(): - with open(HISTORY_FILE, "w") as f: - json.dump(st.session_state.chat_sessions, f, indent=2) - -def get_active_session(): - return st.session_state.chat_sessions[st.session_state.active_session_id] - -def add_message(role, content, **kwargs): - session = get_active_session() - msg = {"role": role, "content": content, "timestamp": time.time()} - msg.update(kwargs) - session["messages"].append(msg) - save_history() - -def new_chat(): - sid = str(uuid.uuid4()) - st.session_state.chat_sessions[sid] = {"title": "New Session", "messages": [], "created_at": time.time()} - st.session_state.active_session_id = sid - save_history() - st.rerun() - -def delete_chat(sid): - if sid in st.session_state.chat_sessions: - del st.session_state.chat_sessions[sid] - if st.session_state.active_session_id == sid: - # Switch to another or create new - if st.session_state.chat_sessions: - st.session_state.active_session_id = list(st.session_state.chat_sessions.keys())[0] - else: - new_chat() - save_history() - st.rerun() - -# ==================================================== -# Login Screen (Simple Overlay) -# ==================================================== -def login_screen(): - c1, c2, c3 = st.columns([1, 2, 1]) - with c2: - st.write("") - st.write("") - st.markdown(""" -
-

🧠 MathMinds AI

-

Your intelligent quantitative assistant.

-
- """, unsafe_allow_html=True) - - - tab_login, tab_signup = st.tabs(["Login", "Sign Up"]) - - with tab_login: - with st.form("login_form"): - email = st.text_input("Email", placeholder="student@university.edu") - password = st.text_input("Password", type="password") - - submitted = st.form_submit_button("Sign In", use_container_width=True) - if submitted: - if email and password: - api_key = os.getenv("FIREBASE_WEB_API_KEY") - if not api_key: - st.error("Missing FIREBASE_WEB_API_KEY in .env") - else: - try: - # Firebase Identity Toolkit API - Login - auth_url = f"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={api_key}" - payload = {"email": email, "password": password, "returnSecureToken": True} - r = requests.post(auth_url, json=payload) - - if r.status_code == 200: - auth_data = r.json() - st.session_state.user = { - "email": auth_data["email"], - "token": auth_data["idToken"], - "uid": auth_data["localId"] - } - st.success(f"Welcome back, {auth_data['email']}!") - time.sleep(0.5) - st.rerun() - else: - err_msg = r.json().get("error", {}).get("message", "Login Failed") - st.error(f"Login Failed: {err_msg}") - except Exception as e: - st.error(f"Connection Error: {e}") - else: - st.error("Please enter email and password.") - - with tab_signup: - with st.form("signup_form"): - new_email = st.text_input("New Email", placeholder="new@student.edu") - new_password = st.text_input("New Password", type="password") - confirm_password = st.text_input("Confirm Password", type="password") - - signup_submitted = st.form_submit_button("Create Account", use_container_width=True) - - if signup_submitted: - if new_email and new_password: - if new_password != confirm_password: - st.error("Passwords do not match!") - else: - api_key = os.getenv("FIREBASE_WEB_API_KEY") - if not api_key: - st.error("Missing FIREBASE_WEB_API_KEY in .env") - else: - try: - # Firebase Identity Toolkit API - Sign Up - auth_url = f"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={api_key}" - payload = {"email": new_email, "password": new_password, "returnSecureToken": True} - r = requests.post(auth_url, json=payload) - - if r.status_code == 200: - auth_data = r.json() - st.session_state.user = { - "email": auth_data["email"], - "token": auth_data["idToken"], - "uid": auth_data["localId"] - } - st.success(f"Account Created! Welcome, {auth_data['email']}!") - time.sleep(0.5) - st.rerun() - else: - err_msg = r.json().get("error", {}).get("message", "Sign Up Failed") - st.error(f"Sign Up Failed: {err_msg}") - except Exception as e: - st.error(f"Connection Error: {e}") - else: - st.error("Please fill all fields.") - - st.markdown("

Powered by Gemini 1.5 Pro & SymPy

", unsafe_allow_html=True) - -if not st.session_state.user: - login_screen() - st.stop() # Stop rendering the rest until logged in - -# ==================================================== -# Main App Layout -# ==================================================== - -# ==================================================== -# Main App Logic -# ==================================================== - -def profile_interface(): - st.title("👤 User Profile") - st.markdown("Customize your MathMinds experience.") - - # Fetch current profile - headers = {"Authorization": f"Bearer {st.session_state.user['token']}"} - - # Use session state to avoid re-fetching on every rerun if possible, - # but for simplicity we fetch or check state - if "profile_data" not in st.session_state: - try: - r = requests.get(f"{API_URL.replace('/solve', '')}/users/profile", headers=headers) - if r.status_code == 200: - st.session_state.profile_data = r.json() - else: - st.error("Failed to load profile.") - st.session_state.profile_data = {} - except Exception as e: - st.error(f"Connection error: {e}") - st.session_state.profile_data = {} - - data = st.session_state.profile_data - - with st.form("profile_form"): - display_name = st.text_input("Display Name", value=data.get("display_name", "")) - math_level = st.selectbox( - "Math Proficiency Level", - ["High School", "Undergraduate", "Graduate", "Researcher"], - index=["High School", "Undergraduate", "Graduate", "Researcher"].index(data.get("math_level", "Undergraduate")) if data.get("math_level") in ["High School", "Undergraduate", "Graduate", "Researcher"] else 1 - ) - - current_interests = data.get("interests", []) - all_interests = ["Algebra", "Calculus", "Geometry", "Statistics", "Physics", "Computer Science", "Finance"] - interests = st.multiselect("Areas of Interest", all_interests, default=[i for i in current_interests if i in all_interests]) - - submitted = st.form_submit_button("Save Profile", use_container_width=True, type="primary") - - if submitted: - payload = { - "display_name": display_name, - "math_level": math_level, - "interests": interests - } - try: - r = requests.post(f"{API_URL.replace('/solve', '')}/users/profile", json=payload, headers=headers) - if r.status_code == 200: - st.success("Profile updated successfully!") - st.session_state.profile_data = payload # Update local cache - time.sleep(1) - st.rerun() - else: - st.error(f"Update failed: {r.text}") - except Exception as e: - st.error(f"Error saving: {e}") - -def chat_interface(): - # --- Active Chat Interface --- - if st.session_state.active_session_id not in st.session_state.chat_sessions: - new_chat() # fallback - - st.title(st.session_state.chat_sessions[st.session_state.active_session_id]["title"]) - - # 1. Render Chat History - session = get_active_session() - for msg in session["messages"]: - if msg["role"] == "user": - with st.chat_message("user", avatar="👤"): - if msg.get("image_data"): - st.image(base64.b64decode(msg["image_data"]), width=300) - st.write(msg["content"]) - else: - with st.chat_message("assistant", avatar="🤖"): - # Metadata Badges - meta = msg.get("metadata", {}) - if meta: - badges_html = "" - if meta.get("source") == "deterministic": - badges_html += '⚡ SYMBOLIC' - elif meta.get("source") == "cache": - badges_html += '💾 CACHED' - elif meta.get("source") == "agent": - badges_html += '🤖 AGENT' - - model = meta.get("model_used") - if model: - badges_html += f'{model}' - - st.markdown(badges_html, unsafe_allow_html=True) - - # Content - content = msg["content"] - - # If complex reasoning exists - reasoning = msg.get("reasoning") - if reasoning: - with st.expander("Show Reasoning Steps"): - st.markdown(reasoning) - - # Final Answer - if isinstance(content, dict) and "final_answer" in content: - st.markdown(f"**Answer:**\n\n> {content['final_answer']}") - else: - st.markdown(content) - - - # 2. Input Area (Tabs for Text / Image / Canvas) - st.divider() - tab_text, tab_draw, tab_upload = st.tabs(["💬 Text", "✏️ Draw", "📤 Upload"]) - - prompt = None - image_b64 = None - - with tab_text: - prompt = st.chat_input("Ask a math question...") - - with tab_draw: - col_canvas, col_controls = st.columns([3, 1]) - with col_canvas: - # Drawing Canvas - if "canvas_key" not in st.session_state: st.session_state.canvas_key = "main_canvas" - - canvas_result = st_canvas( - stroke_width=3, - stroke_color="#FFFFFF", - background_color="#000000", - height=300, - width=600, - drawing_mode="freedraw", - key=st.session_state.canvas_key, - ) - with col_controls: - st.caption("Controls") - if st.button("Clear Canvas"): - st.session_state.canvas_key = f"canvas_{uuid.uuid4()}" - st.rerun() - - if st.button("Solve Drawing", type="primary"): - if canvas_result.image_data is not None: - # Convert to b64 - img = Image.fromarray(canvas_result.image_data.astype("uint8"), "RGBA") - # Composite over black - bg = Image.new("RGB", img.size, (0, 0, 0)) - bg.paste(img, mask=img.split()[3]) - - buf = io.BytesIO() - bg.save(buf, format="PNG") - image_b64 = base64.b64encode(buf.getvalue()).decode() - prompt = "Solve this handwritten math problem." - - with tab_upload: - uploaded = st.file_uploader("Upload Image", type=["png", "jpg"]) - if uploaded and st.button("Analyze Image"): - image_b64 = base64.b64encode(uploaded.getvalue()).decode() - prompt = "Analyze this image." - - - # 3. Processing Logic - if prompt: - # Optimistic UI Update - add_message("user", prompt, image_data=image_b64) - st.rerun() - - # Check if last message was user, trigger AI response - if session["messages"] and session["messages"][-1]["role"] == "user": - last_msg = session["messages"][-1] - - with st.chat_message("assistant", avatar="🤖"): - with st.spinner("Agent is thinking... (Calling tools)"): - try: - # Payload - payload = { - "text": last_msg["content"], - "image": last_msg.get("image_data"), - "model_preference": "agent", - "session_id": st.session_state.active_session_id - } - - # HEADERS with Auth - headers = {} - if st.session_state.user: - headers["Authorization"] = f"Bearer {st.session_state.user['token']}" - - response = requests.post(API_URL, json=payload, headers=headers, timeout=120) - data = response.json() - - if response.status_code == 200: - data = response.json() - - if data.get("status") == "success": - # Standardize answer extraction - answer_raw = data.get("answer") - content_to_save = "" - - if isinstance(answer_raw, dict): - content_to_save = answer_raw.get("final_answer") or answer_raw.get("text") or str(answer_raw) - else: - content_to_save = str(answer_raw) - - # Use top-level explanation from schema - explanation = data.get("explanation") - - add_message( - "assistant", - content_to_save, - reasoning=explanation, - metadata=data.get("metadata"), - steps=data.get("steps") # New field - ) - st.rerun() - else: - error_msg = data.get("error", "Unknown error") - st.error(f"API Error: {error_msg}") - add_message("assistant", f"⚠️ Error: {error_msg}") - st.rerun() # Ensure we close the turn - else: - st.error(f"Server Error: {response.status_code}") - # Try to parse error details - try: - err_data = response.json() - err_msg = err_data.get("error", "Unknown Server Error") - except: - err_msg = f"HTTP {response.status_code}" - - add_message("assistant", f"⚠️ Server Error: {err_msg}") - st.rerun() - except Exception as e: - st.error(f"Connection Failed: {e}") - # CRITICAL FIX: Add error message to history to stop retry loop - add_message("assistant", f"❌ Connection Failed: {str(e)}") - st.rerun() - -# --- Initialize View State --- -if "current_view" not in st.session_state: - st.session_state.current_view = "Chat" - -# --- Sidebar (History & Settings) --- -with st.sidebar: - st.markdown("### 🧠 MathMinds") - - st.write(f"Logged in as **{st.session_state.user['email']}**") - - # Navigation - view = st.radio("Navigation", ["Chat", "Profile"], index=0 if st.session_state.current_view == "Chat" else 1) - if view != st.session_state.current_view: - st.session_state.current_view = view - st.rerun() - - if st.button("Sign Out", type="secondary"): - st.session_state.user = None - st.rerun() - - st.divider() - - if st.session_state.current_view == "Chat": - if st.button("➕ New Chat", use_container_width=True, type="primary"): - new_chat() - - st.markdown("#### History") - - # Sort by recent - sorted_sids = sorted( - st.session_state.chat_sessions.keys(), - key=lambda k: st.session_state.chat_sessions[k].get("created_at", 0), - reverse=True - ) - - for sid in sorted_sids: - sess = st.session_state.chat_sessions[sid] - title = sess.get("title", "Untitled") - isActive = (sid == st.session_state.active_session_id) - - col_nav, col_del = st.columns([0.85, 0.15]) - with col_nav: - if st.button(f"{'📍 ' if isActive else ''}{title}", key=sid, use_container_width=True): - st.session_state.active_session_id = sid - st.rerun() - with col_del: - if isActive: - if st.button("🗑️", key=f"del_{sid}"): - delete_chat(sid) - -# --- Router --- -if st.session_state.current_view == "Profile": - profile_interface() -else: - chat_interface() - - - - -================================================================================ -FILE: e:\madhuri\mathminds\scripts\clear_cache.py -================================================================================ - - -import asyncio -import sys -import os -import logging -from dotenv import load_dotenv - -# Load env vars -load_dotenv(override=True) - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -from app.memory.cache import CacheManager - -# Configure Logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("cache_cleaner") - -def clear_cache(): - print("Cleaning Redis Cache...") - try: - cache = CacheManager() - if cache.redis_client: - cache.redis_client.flushall() - print("Cache Flushed Successfully!") - else: - print("Redis not connected.") - except Exception as e: - print(f"Error clearing cache: {e}") - -if __name__ == "__main__": - clear_cache() - - -================================================================================ -FILE: e:\madhuri\mathminds\scripts\evaluate_system.py -================================================================================ - -import asyncio -import json -import logging -import time -import sys -import os -from dotenv import load_dotenv - -# Load env vars -load_dotenv(override=True) - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -if sys.platform == 'win32': - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - -from app.core.orchestrator import Orchestrator - -# Configure Logging -logging.basicConfig(level=logging.INFO, format='%(name)s - %(levelname)s - %(message)s') -logger = logging.getLogger("evaluator") - -DATASET_PATH = "tests/data/evaluation_dataset.json" - -async def run_evaluation(): - print(f"🚀 Starting System Evaluation using {DATASET_PATH}...") - - try: - with open(DATASET_PATH, "r") as f: - dataset = json.load(f) - except FileNotFoundError: - print(f"❌ Dataset not found at {DATASET_PATH}") - return - - orchestrator = Orchestrator() - - results = [] - - print(f"Loaded {len(dataset)} test cases.\n") - print(f"{'ID':<10} | {'Category':<15} | {'Model Expected':<15} | {'Model Used':<15} | {'Latency':<10} | {'Status'}") - print("-" * 90) - - for case in dataset: - case_id = case["id"] - category = case["category"] - text_input = case["input"] - expected_model = case.get("expected_model") - expected_keywords = case.get("expected_keywords", []) - expected_error = case.get("expected_error", False) - - start_ts = time.time() - - # Call System - try: - response = await orchestrator.process_problem(text=text_input) - latency = time.time() - start_ts - - # Checks - status = "PASS" - fail_reasons = [] - - # 1. Error Check - if expected_error: - if "error" not in response and response.get("status") == "success": - status = "FAIL" - fail_reasons.append("Expected error, got success") - else: - if response.get("status") == "error": - status = "FAIL" - fail_reasons.append(f"Error: {response.get('error') or response.get('error_msg')}") - - # 2. Answer Check - answer_text = str(response.get("answer", "") or "") - model_used = response.get("metadata", {}).get("model_used", "unknown") - - # Check keywords - for kw in expected_keywords: - if kw.lower() not in answer_text.lower(): - status = "FAIL" - fail_reasons.append(f"Missing keyword '{kw}'") - break - - # 3. Model Check (Loose check, just warning if mismatch often) - # Note: Qwen might be 'qwen2.5-math' or similar in metadata - if expected_model and expected_model not in model_used: - # Don't fail hard on model swap if answer is correct, but note it - # status = "WARN" # Optional - pass - - # Print Row - model_display = response.get("metadata", {}).get("model_used", "N/A") - latency_display = f"{latency:.2f}s" - - row_color = "" - if status == "FAIL": - row_color = "❌ " - else: - row_color = "✅ " - - print(f"{row_color}{case_id:<8} | {category:<15} | {expected_model or 'Any':<15} | {model_display:<15} | {latency_display:<10} | {status}") - - if status == "FAIL": - print(f" Details: {fail_reasons}") - print(f" Output: {answer_text[:100]}...") - - results.append({ - "id": case_id, - "latency": latency, - "status": status, - "model_used": model_display - }) - - except Exception as e: - print(f"❌ {case_id:<8} | CRITICAL ERROR: {e}") - - # Summary - print("-" * 90) - passed = len([r for r in results if r["status"] == "PASS"]) - total = len(dataset) - print(f"🏁 Evaluation Complete. Passed: {passed}/{total} ({passed/total*100:.1f}%)") - -if __name__ == "__main__": - asyncio.run(run_evaluation()) - - -================================================================================ -FILE: e:\madhuri\mathminds\scripts\list_models.py -================================================================================ - -import os -from google import genai -from dotenv import load_dotenv - -load_dotenv() - -api_key = os.getenv("GOOGLE_API_KEY") -if not api_key: - print("No API Key found") - exit(1) - -client = genai.Client(api_key=api_key) - -try: - print("Listing models...") - # The SDK might have a slightly different list method depending on version - # Trying standard approach - for m in client.models.list(config={"page_size": 100}): - # Debug: print available fields if needed, or just list all - # To avoid noise, let's just print names. - print(f"Model: {m.name}") - # print(dir(m)) # Uncomment if desperate -except Exception as e: - print(f"Error: {e}") - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_adk_agent.py -================================================================================ - - -import asyncio -import os -import logging -from dotenv import load_dotenv - -# Load environment variables -load_dotenv() - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -# Import Google ADK components -from google.adk.agents.llm_agent import LlmAgent -from google.adk.models.google_llm import Gemini -from google.adk.tools.function_tool import FunctionTool -from google.adk.runners import Runner -from google.adk.sessions.in_memory_session_service import InMemorySessionService -from google.genai import types - -# Define a simple tool -def calculator(a: int, b: int, op: str) -> int: - """Performs simple arithmetic operations. - - Args: - a: The first number. - b: The second number. - op: The operation ('+', '-', '*', '/'). - """ - logger.info(f"Calculator called with a={a}, b={b}, op={op}") - if op == '+': - return a + b - elif op == '-': - return a - b - elif op == '*': - return a * b - elif op == '/': - return int(a / b) - return 0 - -async def main(): - print("Initializing Google ADK Agent...") - - # 1. Create Model - # Ensure GOOGLE_API_KEY is set in .env - model = Gemini(model="gemini-2.5-flash") - - # 2. Create Tools - calc_tool = FunctionTool(calculator) - - # 3. Create Agent - agent = LlmAgent( - name="math_helper", - model=model, - tools=[calc_tool], - instruction="You are a helpful math assistant. Use the calculator tool for computations." - ) - - # 4. Create Services - session_service = InMemorySessionService() - - # 5. Create Runner - runner = Runner( - app_name="mathminds_adk_test", - agent=agent, - session_service=session_service - ) - - # 6. Run Agent - user_id = "test_user" - session_id = "test_session" - prompt = "Calculate 15 * 12 then add 50." - - print(f"\nUser: {prompt}") - - # Creating a new session explicitly if needed, but runner might handle it. - # Runner.run requires session to exist? define user_id and session_id. - # InMemorySessionService usually auto-creates if logic allows, checking Runner code... - # Runner.run check: session_service.get_session returns None -> ValueError "Session not found" - # So we must create session first. - - session = await session_service.create_session( - app_name="mathminds_adk_test", - user_id=user_id, - session_id=session_id - ) - - print("Session created. Running agent...") - - # Using run_async for better control - response_text = "" - async for event in runner.run_async( - user_id=user_id, - session_id=session_id, - new_message=types.Content(role="user", parts=[types.Part.from_text(text=prompt)]) - ): - # Inspect event types - if event.content and event.content.parts: - for part in event.content.parts: - if part.text: - print(f"Agent partial: {part.text}") - response_text += part.text - if part.function_call: - print(f"Tool Call: {part.function_call.name}({part.function_call.args})") - if part.function_response: - print(f"Tool Result: {part.function_response.response}") - - print(f"\nFinal Response: {response_text}") - -if __name__ == "__main__": - asyncio.run(main()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_adk_full.py -================================================================================ - - -import asyncio -import os -import logging -from dotenv import load_dotenv - -# Load environment variables -load_dotenv() - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -import sys -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -# Import the new agent -from app.agents.adk_mathminds import MathMindsADKAgent - -async def main(): - print("Initializing MathMinds ADK Agent...") - - try: - agent = MathMindsADKAgent() - - # Test 1: Math Problem - prompt_math = "Calculate the derivative of x^2 + 5x." - print(f"\nUser: {prompt_math}") - response_math = await agent.solve(prompt_math) - print(f"Agent Response: {response_math}") - - # Test 2: General Knowledge (Web Search) - # Note: Web scraping might fail if not configured or blocked, but agent should handle it gracefully. - prompt_search = "What is the capital of France?" - print(f"\nUser: {prompt_search}") - response_search = await agent.solve(prompt_search) - print(f"Agent Response: {response_search}") - - except Exception as e: - logger.error(f"Test failed: {e}") - -if __name__ == "__main__": - asyncio.run(main()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_adk_poc.py -================================================================================ - - -import asyncio -import os -import sys -import logging -from dotenv import load_dotenv - -# Fix path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) -load_dotenv() - -logging.basicConfig(level=logging.INFO) - -try: - from google.adk.agents import LlmAgent - from google.adk.runners import Runner - from google.adk.sessions.in_memory_session_service import InMemorySessionService - from google.genai import types -except ImportError as e: - print(f"Import Error: {e}") - sys.exit(1) - -async def main(): - print("Initializing components...") - - # 1. Agent - # Note: LlmAgent arguments might strictly be pydantic fields. - # If 'model' is expected to be a string alias, this works. - agent = LlmAgent( - name="MathTest", - model="gemini-2.5-flash", - instruction="You are a helpful math assistant." - ) - - # 2. Session Service - session_service = InMemorySessionService() - - # 3. Runner - # Assuming Runner takes agent and session_service. - # If it needs 'model' client explicitly, we might fail here. - try: - runner = Runner(agent=agent, app_name="MathMindsPoC", session_service=session_service) - except TypeError as e: - print(f"Runner init failed: {e}") - # Maybe it takes arguments differently? - return - - print("Setting up session...") - session_id = "poc_session" - user_id = "poc_user" - - try: - await session_service.create_session( - app_name="MathMindsPoC", - user_id=user_id, - session_id=session_id - ) - except Exception as e: - print(f"Session creation failed: {e}") - return - - print("Running agent...") - - try: - async for event in runner.run_async( - user_id=user_id, - session_id=session_id, - new_message=types.Content( - role="user", - parts=[types.Part.from_text(text="What is 10 * 10?")] - ) - ): - # Print the event to see structure - print(f"Event: {type(event)}") - if hasattr(event, 'content') and event.content: - for part in event.content.parts: - if part.text: - print(f"Text: {part.text}") - - except Exception as e: - print(f"Run Error: {e}") - import traceback - traceback.print_exc() - -if __name__ == "__main__": - asyncio.run(main()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_api_integration.py -================================================================================ - - -import asyncio -import os -import sys -import logging -from dotenv import load_dotenv - -# Fix path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -# Load environment variables -load_dotenv() - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -from app.core.orchestrator import Orchestrator - -async def main(): - print("Initializing Orchestrator...") - try: - orchestrator = Orchestrator() - - # Test: Arithmetic via Agent - prompt = "Calculate 15 * 3." - print(f"\nUser: {prompt} (Preference: agent)") - - result = await orchestrator.process_problem( - text=prompt, - model_preference="agent", - request_id="test_req_1" - ) - - print("\nResult:") - print(f"Status: {result.get('status')}") - print(f"Source: {result.get('source')}") - print(f"Answer: {result.get('answer')}") - print(f"Model: {result.get('metadata', {}).get('model')}") - - if result.get("source") == "google_adk_agent": - print("\nSUCCESS: Routed to Google ADK Agent.") - else: - print("\nFAILURE: Did not route to Google ADK Agent.") - - except Exception as e: - logger.error(f"Test failed: {e}") - import traceback - traceback.print_exc() - -if __name__ == "__main__": - asyncio.run(main()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_hashing.py -================================================================================ - -import unittest -import sys -import os - -# Ensure app is in path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from app.utils.hashing import generate_problem_hash - -class TestHashing(unittest.TestCase): - def test_basic_consistency(self): - """Test that same input produces same hash.""" - input1 = "Calculate the integral of x^2" - input2 = "Calculate the integral of x^2" - self.assertEqual(generate_problem_hash(input1), generate_problem_hash(input2)) - - def test_normalization_whitespace(self): - """Test that whitespace differences are ignored.""" - input1 = " Calculate the integral of x^2 " - input2 = "calculate the integral of x^2" - # The implementation lowercases and strips, and normalizes space? - # Let's check logic: "normalized_text.lower().strip()". - # It does NOT replace inner multiple spaces with single space unless I added that regex. - # Checking my implementation of hashing.py... - # It was: - # normalized_text = unicodedata.normalize('NFKC', text) - # cleaned_text = normalized_text.lower().strip() - # It does NOT collapse internal whitespace. So " a b " -> "a b". - # Wait, let me check strict equality. - - # If I want robustness, I usually want to collapse whitespace input_processor does that. - # But hashing utility just does basic strip. - # Orchestrator calls input_processor FIRST. - # InputProcessor.normalize_text: text = re.sub(r'\s+', ' ', text). - # So Hashing util receives ALREADY normalized text potentially. - # But Hashing Util should be robust on its own? - # The prompt for hashing said "Normalize before hashing". - # My implementation: - # normalized_text = unicodedata.normalize('NFKC', text) - # cleaned_text = normalized_text.lower().strip() - - # So internal whitespace is preserved. - # So "A B" hash != "A B" hash. - # Adjusting test expectation to match implementation or updating implementation if desired? - # User requirement for hashing was "Normalize before hashing". - # Usually implies whitespace collapsing. - # But let's test what IS implemented first. - - # Actually, let's write the test to verify what happens. - pass - - def test_unicode_normalization(self): - """Test NFKC normalization.""" - # e.g. 2^2 vs 2² - # NFKC normalizes compatibility characters. - input1 = "x²" - input2 = "x2" # Wait, squared symbol usually normalizes to '2' followed by something? - # Actually \u00B2 (SUPERSCRIPT TWO) -> '2'. - # So "x\u00B2" might become "x2". - - self.assertEqual(generate_problem_hash("x\u00B2"), generate_problem_hash("x2")) - - def test_empty_input(self): - """Test that empty input raises ValueError.""" - with self.assertRaises(ValueError): - generate_problem_hash("") - with self.assertRaises(ValueError): - generate_problem_hash(None) - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_input_processor.py -================================================================================ - -import unittest -import sys -import os - -# Ensure app is in path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from app.core.input_processor import InputProcessor, InputType - -class TestInputProcessor(unittest.TestCase): - def setUp(self): - self.processor = InputProcessor() - - def test_text_normalization(self): - """Test basic text normalization.""" - raw_input = " HELLO World " - result = self.processor.process(raw_input) - self.assertTrue(result.is_valid) - self.assertEqual(result.input_type, InputType.TEXT) - self.assertEqual(result.cleaned_content, "hello world") - - def test_latex_detection(self): - """Test detection of LaTeX content.""" - latex_input = "Solve $x^2 + 2x + 1 = 0$" - result = self.processor.process(latex_input) - self.assertTrue(result.is_valid) - self.assertEqual(result.input_type, InputType.LATEX) - # Normalization should still prevent dangerous stuff but allow latex chars - self.assertIn("solve $x^2 + 2x + 1 = 0$", result.cleaned_content) - - def test_image_url_detection(self): - """Test detection of image URLs.""" - url_input = "https://example.com/equation.png" - result = self.processor.process(url_input) - self.assertTrue(result.is_valid) - self.assertEqual(result.input_type, InputType.IMAGE_URL) - self.assertEqual(result.cleaned_content, url_input) - - def test_base64_detection(self): - """Test detection of base64 images.""" - base64_input = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" - result = self.processor.process(base64_input) - self.assertTrue(result.is_valid) - self.assertEqual(result.input_type, InputType.BASE64_IMAGE) - - def test_dangerous_content(self): - """Test rejection of dangerous content.""" - dangerous_inputs = [ - "", - "javascript:alert(1)", - "DROP TABLE users", - "UNION SELECT * FROM passwords" - ] - for inp in dangerous_inputs: - result = self.processor.process(inp) - self.assertFalse(result.is_valid, f"Should reject: {inp}") - self.assertIn("potentially dangerous", result.error_message) - - def test_empty_input(self): - """Test empty input handling.""" - result = self.processor.process("") - self.assertFalse(result.is_valid) - self.assertEqual(result.input_type, InputType.UNKNOWN) - - def test_max_length(self): - """Test max length enforcement.""" - long_input = "a" * 5001 - result = self.processor.process(long_input) - self.assertFalse(result.is_valid) - self.assertIn("length exceeds", result.error_message) - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_math_pipeline.py -================================================================================ - -import asyncio -import os -import sys - -# Add project root to path -sys.path.append(os.getcwd()) - -from app.core.math_normalizer import MathQueryNormalizer -from app.tools.symbolic_solver import SymbolicSolver - -async def test_pipeline(): - print("--- Testing Math Normalizer ---") - normalizer = MathQueryNormalizer() - - queries = [ - "what is the derivative of tan(x)?", - "integrate x^2", - "solve 2*x + 5 = 15", - "calculate 5 + 3 * 2" - ] - - intents = [] - - for q in queries: - intent = normalizer.normalize(q) - print(f"Query: '{q}'") - if intent: - print(f" -> Intent: {intent.intent}, Expression: '{intent.expression}'") - intents.append(intent) - else: - print(" -> NO INTENT DETECTED") - - print("\n--- Testing Symbolic Solver (SymPy fallback) ---") - solver = SymbolicSolver() - - for intent in intents: - print(f"Solving: {intent.intent} -> {intent.expression}") - res = solver.solve(intent) - print(f" Result: {res}") - -if __name__ == "__main__": - asyncio.run(test_pipeline()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_memory_manual.py -================================================================================ - -import asyncio -import sys -import os -import logging -from datetime import datetime - -# Add project root to path -sys.path.append(os.getcwd()) - -from app.memory.database import DatabaseManager - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -class MockCollection: - def __init__(self): - self.data = {} # Keyed by session_id for simplicity in this specific test - - def create_indexes(self, indexes): - pass - - def update_one(self, filter, update, upsert=False): - session_id = filter.get("session_id") - if not session_id: return - - if session_id not in self.data: - if upsert: - # Initialize with setOnInsert - self.data[session_id] = update.get("$setOnInsert", {}) - else: - return # No doc to update - - doc = self.data[session_id] - - # Handle $push - if "$push" in update: - for key, val in update["$push"].items(): - if key not in doc: doc[key] = [] - doc[key].append(val) - - # Handle $set - if "$set" in update: - for key, val in update["$set"].items(): - doc[key] = val - - def find_one(self, filter, projection=None): - session_id = filter.get("session_id") - doc = self.data.get(session_id) - if not doc: return None - - # Handle slice projection purely for 'messages' - if projection and "messages" in projection: - if "$slice" in projection["messages"]: - limit = projection["messages"]["$slice"] - # simplified slice handling - start = limit if limit < 0 else 0 - # actually python list slicing for -10 is different. - # Mongo slice -10 means "last 10". Python list[-10:] means last 10. - if limit < 0: - doc_copy = doc.copy() - doc_copy["messages"] = doc["messages"][limit:] - return doc_copy - return doc - -class MockDb: - def __init__(self): - self.collections = {"chat_sessions": MockCollection(), "solved_problems": MockCollection()} - - def __getitem__(self, key): - return self.collections[key] - -class MockClient: - def __init__(self, *args, **kwargs): - self.db = MockDb() - - def __getitem__(self, key): - return self.db - - def server_info(self): - return {"version": "mock"} - -async def test_session_lifecycle(): - print("\n--- Test 1: Session Lifecycle (with Mock DB) ---") - - # Inject Mock Client - mock_client = MockClient() - db = DatabaseManager(client=mock_client) - # Manually set db property because __init__ does some logic - db.db = mock_client["mathminds_ai"] - db.collection = db.db["solved_problems"] - - session_id = "test_sess_001" - print(f"Session ID: {session_id}") - - # 1. Create Session - print("Creating session...") - created = db.create_session(session_id) - print(f"Created: {created}") - - # 2. Add First User Query (Should update title) - query1 = "What is the derivatives of sin(x)?" - print(f"Saving User Query: {query1}") - db.save_chat_message(session_id, "user", query1) - - # Check Title - doc = db.db["chat_sessions"].find_one({"session_id": session_id}) - print(f"Session Title: {doc.get('title')}") - assert doc.get('title') == query1, f"Title mismatch. Expected '{query1}', got '{doc.get('title')}'" - - # 3. Add AI Response - response1 = "The derivative of sin(x) is cos(x)." - print(f"Saving AI Response: {response1}") - db.save_chat_message(session_id, "model", response1) - - # 4. Get History - history = db.get_chat_history(session_id) - print(f"History Length: {len(history)}") - - assert len(history) == 2, "Should have 2 messages" - - # 5. Add Second User Query (Should NOT update title) - query2 = "What about cos(x)?" - db.save_chat_message(session_id, "user", query2) - - doc_after = db.db["chat_sessions"].find_one({"session_id": session_id}) - print(f"Session Title (After 2nd Query): {doc_after.get('title')}") - assert doc_after.get('title') == query1, "Title should not change on second query" - - print("\n✅ Verification Passed (Mock Mode)!") - -if __name__ == "__main__": - try: - asyncio.run(test_session_lifecycle()) - except Exception as e: - print(f"❌ Test Failed: {e}") - import traceback - traceback.print_exc() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_multimodal_mock.py -================================================================================ - - -import pytest -from unittest.mock import MagicMock, AsyncMock, patch -from app.core.orchestrator import Orchestrator -from app.core.schemas import SolveRequest -from app.reasoning.gemini_client import GeminiSolver - -@pytest.mark.asyncio -async def test_orchestrator_routes_model_preference(): - """ - Verify that Orchestrator correctly routes requests to GeminiSolver - with the specified model preference. - """ - # Mock dependencies - mock_input_processor = MagicMock() - mock_input_processor.process_compound.return_value.is_valid = True - mock_input_processor.process_compound.return_value.cleaned_content = "Solve this" - mock_input_processor.process_compound.return_value.metadata = {} - - mock_solver = AsyncMock(spec=GeminiSolver) - mock_solver.solve.return_value = {"answer": "x=2", "latex": "x=2", "reasoning": "steps...", "final_answer": "x=2", "confidence_score": 1.0} - - mock_cache = MagicMock() - mock_cache.get_cached_answer.return_value = None - - mock_db = MagicMock() - mock_db.find_by_hash.return_value = None - - # Instantiate Orchestrator with mocks - with patch("app.core.orchestrator.InputProcessor", return_value=mock_input_processor), \ - patch("app.core.orchestrator.GeminiSolver", return_value=mock_solver), \ - patch("app.core.orchestrator.CacheManager", return_value=mock_cache), \ - patch("app.core.orchestrator.DatabaseManager", return_value=mock_db), \ - patch("app.core.orchestrator.QueryRouter"), \ - patch("app.core.orchestrator.WebScraper"), \ - patch("app.core.orchestrator.SymbolicSolver"), \ - patch("app.core.orchestrator.QueryClassifier"): - - orchestrator = Orchestrator() - - # Test Case 1: Fast Model - await orchestrator.process_problem(text="Test 1", model_preference="fast") - # Verify solver called with flash - mock_solver.solve.assert_called_with( - "Solve this", - image_data=None, - model_name="gemini-2.5-flash" - ) - - # Test Case 2: Reasoning Model - await orchestrator.process_problem(text="Test 2", model_preference="reasoning") - # Verify solver called with pro - mock_solver.solve.assert_called_with( - "Solve this", - image_data=None, - model_name="gemini-2.5-flash" - ) - - print("\n[PASS] Orchestrator Routing Test Passed!") - -if __name__ == "__main__": - import asyncio - asyncio.run(test_orchestrator_routes_model_preference()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_new_redis_cache.py -================================================================================ - -import unittest -import sys -import os -import time - -# Ensure backend path is included -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from backend.cache.redis_cache import RedisCache - -class TestNewRedisCache(unittest.TestCase): - def setUp(self): - self.cache = RedisCache() - # Check connection - if not self.cache.client: - self.skipTest("Redis not available") - - def test_set_get(self): - key = "test_new_cache" - val = {"foo": "bar"} - self.assertTrue(self.cache.set(key, val)) - self.assertEqual(self.cache.get(key), val) - - def test_expiry(self): - key = "test_expiry" - val = "temp" - self.cache.set(key, val, ttl=1) - time.sleep(1.2) - self.assertIsNone(self.cache.get(key)) - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_ocr.py -================================================================================ - -import sys -import os -import unittest -import base64 -from unittest.mock import MagicMock, patch - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -from app.core.input_processor import InputProcessor, InputType -from app.core.ocr import OCRProcessor - -class TestOCR(unittest.TestCase): - def setUp(self): - # We process input which instantiates OCRProcessor - # We need to mock OCRProcessor internal paddleocr to avoid download - self.processor = InputProcessor() - - @patch('app.core.ocr.PaddleOCR') - @patch('app.core.ocr.Image') # Mock Pillow Image - def test_base64_ocr_success(self, mock_image, mock_paddle): - # Setup mocks - mock_ocr_instance = MagicMock() - mock_paddle.return_value = mock_ocr_instance - - # Mock OCR result: list of lines, where each line is list of words, where word is [box, [text, score]] - # PaddleOCR result format is complex: [ [ [ [x,y]..], ("text", score) ] ... ] ] - # Our code expects: result[0][i][1][0] is text - mock_ocr_instance.ocr.return_value = [[ - [None, ("1+1", 0.99)], - [None, ("=", 0.98)], - [None, ("?", 0.95)] - ]] - - # Mock valid image check - mock_img_instance = MagicMock() - # Ensure format is allowed - type(mock_img_instance).format = unittest.mock.PropertyMock(return_value='PNG') - mock_image.open.return_value = mock_img_instance - - # Create a dummy base64 string (valid format but dummy content) - b64_str = "data:image/png;base64," + base64.b64encode(b"dummydata").decode('utf-8') - - # Inject our mocked ocr processor into the input processor - # (Since InputProcessor initializes it in init, we can replace it OR verify usage) - # Better: let's just patch OCRProcessor class used by InputProcessor? - # But InputProcessor already instantiated in setUp. - # Let's replace the instance. - - ocr_proc = OCRProcessor() - ocr_proc.ocr = mock_ocr_instance # Inject mock paddle - ocr_proc._enabled = True - self.processor.ocr_processor = ocr_proc - - result = self.processor.process(b64_str) - - self.assertTrue(result.is_valid) - self.assertEqual(result.input_type, InputType.BASE64_IMAGE) - self.assertEqual(result.cleaned_content, "1+1 = ?") - - def test_invalid_image_data(self): - # invalid base64 - result = self.processor.process("data:image/png;base64,INVALID_STUFF") - self.assertFalse(result.is_valid) - self.assertEqual(result.input_type, InputType.BASE64_IMAGE) # Detects type but fails processing - self.assertIn("Failed to process", result.error_message) - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_rate_limiting.py -================================================================================ - - -import sys -import os -import requests -import time - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -BASE_URL = "http://127.0.0.1:8000" - -def test_rate_limiting(): - print("Testing Rate Limiting...") - - # Ensure service is up - try: - resp = requests.get(f"{BASE_URL}/health") - if resp.status_code != 200: - print("Server not healthy. Is it running?") - return - except requests.exceptions.ConnectionError: - print("Could not connect to server. Make sure it is running on port 8000.") - return - - # Send 5 requests (should succeed) - for i in range(1, 6): - print(f"Sending request {i}...") - resp = requests.post(f"{BASE_URL}/solve", json={"input": "1+1"}) - - if resp.status_code == 200: - print(f"Request {i}: Success") - elif resp.status_code == 422: - print(f"Request {i}: Validation Error (Expected if input invalid, distinct from 429)") - else: - print(f"Request {i}: Unexpected status {resp.status_code}") - print(resp.text) - - # Send 6th request (should fail) - print("Sending request 6 (Should fail with 429)...") - resp = requests.post(f"{BASE_URL}/solve", json={"input": "1+1"}) - - if resp.status_code == 429: - print("PASS: Request 6 was rate limited (429).") - else: - print(f"FAIL: Request 6 status was {resp.status_code}.") - print(resp.text) - -if __name__ == "__main__": - test_rate_limiting() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_redis_cache.py -================================================================================ - -import unittest -import sys -import os -import time - -# Ensure app is in path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from app.memory.cache import CacheManager - -class TestRedisCache(unittest.TestCase): - def setUp(self): - # We assume the default localhost URL or whatever is in env - # If the user said "real", we try to use what's configured. - self.cache = CacheManager() - if not self.cache.redis_client: - self.skipTest("Redis connection failed. Skipping integration test.") - - def test_set_and_get(self): - """Test setting and getting a value.""" - key = "test_key_123" - data = {"answer": "42", "reasoning": "Deep Thought"} - - # Set - success = self.cache.set_cached_answer(key, data) - self.assertTrue(success, "Failed to set cache key") - - # Get - cached_data = self.cache.get_cached_answer(key) - self.assertEqual(cached_data, data) - - def test_cache_miss(self): - """Test looking up a non-existent key.""" - key = "non_existent_key_999" - # Ensure it's not there - if self.cache.redis_client: - self.cache.redis_client.delete(key) - - result = self.cache.get_cached_answer(key) - self.assertIsNone(result) - - def test_ttl_expiry(self): - """Test that TTL works (short duration).""" - key = "ttl_test_key" - data = {"temp": "data"} - # Set with 1 second TTL - self.cache.set_cached_answer(key, data, ttl=1) - - # Verify immediately - self.assertIsNotNone(self.cache.get_cached_answer(key)) - - # Wait > 1s - time.sleep(1.1) - - # Verify gone - self.assertIsNone(self.cache.get_cached_answer(key), "Key should have expired") - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_resilience.py -================================================================================ - -import sys -import os -import unittest -from unittest.mock import MagicMock, patch - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -import pybreaker -from app.core.orchestrator import Orchestrator -from app.reasoning.gemini_client import GeminiSolver -from app.memory.cache import CacheManager -from app.memory.database import DatabaseManager - -class TestResilience(unittest.TestCase): - - def test_cache_failure_fallback(self): - """Test that Orchestrator falls back to DB when Cache fails.""" - # Setup - mock_cache = MagicMock(spec=CacheManager) - mock_cache.get_cached_answer.side_effect = Exception("Redis Down") - - mock_db = MagicMock(spec=DatabaseManager) - mock_db.find_by_hash.return_value = {"answer": {"final_answer": "42"}} # Mock DB Hit - - orchestrator = Orchestrator(cache_manager=mock_cache, db_manager=mock_db) - - # Action - result = orchestrator.process_problem("What is 6*7?") - - # Assert - self.assertEqual(result["answer"]["final_answer"], "42") - self.assertEqual(result["metadata"]["source"], "database") - print("PASS: Cache failure correctly fell back to DB.") - - def test_db_failure_fallback(self): - """Test that Orchestrator falls back to Solver when DB fails.""" - # Setup - mock_cache = MagicMock(spec=CacheManager) - mock_cache.get_cached_answer.return_value = None # Cache miss - - mock_db = MagicMock(spec=DatabaseManager) - mock_db.find_by_hash.side_effect = Exception("DB Down") - - orchestrator = Orchestrator(cache_manager=mock_cache, db_manager=mock_db) - # Mock the solver to avoid real API calls - orchestrator.solver = MagicMock() - orchestrator.solver.solve.return_value = { - "final_answer": "42", - "latex": "6 \\times 7", - "reasoning": "Standard multiplication", - "confidence_score": 1.0 - } - - # Action - result = orchestrator.process_problem("What is 6*7?") - - # Assert - self.assertEqual(result["answer"]["final_answer"], "42") - self.assertEqual(result["metadata"]["source"], "generated") - print("PASS: DB failure correctly fell back to Solver.") - - def test_circuit_breaker(self): - """Test that Circuit Breaker opens after max failures.""" - solver = GeminiSolver(api_key="fake") - # Ensure breaker is closed initially - solver.breaker.close() - - # Mock internal solve to always fail - solver._solve_internal = MagicMock(side_effect=Exception("API Error")) - - # We need to bypass the @retry decorator for this unit test to test the breaker specifically, - # otherwise we wait for retries. Ideally we test integration, but unit test is faster for verification. - # Alternatively, we just call solve() 6 times. The separate @retry might slow it down, - # so we can mock the checking logic or just reduce retry counts for test. - # Actually, pybreaker wraps the call. We can just call breaker.call directly on a failing func. - - print("Simulating 5 failures...") - for i in range(5): - try: - # We call the breaker directly to test IT, skipping the retry wrapper for speed - solver.breaker.call(solver._solve_internal, "problem") - except Exception: - pass - - # 6th call should raise CircuitBreakerError - print("Simulating 6th failure (Expect CircuitBreakerError)...") - with self.assertRaises(pybreaker.CircuitBreakerError): - solver.breaker.call(solver._solve_internal, "problem") - - print("PASS: Circuit breaker opened after 5 failures.") - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_session_isolation.py -================================================================================ - - -import sys -import os -sys.path.insert(0, os.getcwd()) - -import pytest -import asyncio -from app.agents.adk_mathminds import MathMindsADKAgent - -@pytest.mark.asyncio -async def test_session_isolation(): - """ - Verifies that the ADK agent maintains separate conversation histories - for different session IDs. - """ - agent = MathMindsADKAgent() - - user_id = "test_user_isolation" - session_a = "session_A" - session_b = "session_B" - - print("\n--- Starting Session Isolation Test ---") - - # 1. Seed Session A - print(f"Seeding {session_a} with context 'My name is Alice'...") - resp_a1 = await agent.solve( - problem="My name is Alice. Remember this.", - session_id=session_a, - user_id=user_id - ) - print(f"Agent response (A1): {resp_a1}") - - # 2. Seed Session B - print(f"Seeding {session_b} with context 'My name is Bob'...") - resp_b1 = await agent.solve( - problem="My name is Bob. Remember this.", - session_id=session_b, - user_id=user_id - ) - print(f"Agent response (B1): {resp_b1}") - - # 3. Query Session A - print(f"Querying {session_a} for name...") - resp_a2 = await agent.solve( - problem="What is my name?", - session_id=session_a, - user_id=user_id - ) - print(f"Agent response (A2): {resp_a2}") - - # 4. Query Session B - print(f"Querying {session_b} for name...") - resp_b2 = await agent.solve( - problem="What is my name?", - session_id=session_b, - user_id=user_id - ) - print(f"Agent response (B2): {resp_b2}") - - # Assertions - assert "Alice" in resp_a2, f"Session A failed to remember Alice. Got: {resp_a2}" - assert "Bob" in resp_b2, f"Session B failed to remember Bob. Got: {resp_b2}" - assert "Bob" not in resp_a2, "Session A leaked context from Session B!" - assert "Alice" not in resp_b2, "Session B leaked context from Session A!" - - print("\nSUCCESS: Sessions are isolated correctly!") - -if __name__ == "__main__": - asyncio.run(test_session_isolation()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_smart_routing.py -================================================================================ - -import asyncio -import logging -import sys -import os - -if sys.platform.startswith("win"): - asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from app.core.orchestrator import Orchestrator - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -async def test_routing(): - print("Initializing Orchestrator...") - # Mock cache/db to avoid needing real redis/mongo for this unit test - class MockManager: - def get_cached_answer(self, hash): return None - def set_cached_answer(self, hash, ans): pass - def set_if_not_exists(self, hash, ans): pass - def find_by_hash(self, hash): return None - def save_problem(self, data, solu): pass - - orch = Orchestrator(cache_manager=MockManager(), db_manager=MockManager()) - - # Test 1: Web Route - query_web = "What is the current stock price of Google?" - print(f"\n--- Testing Web Route: '{query_web}' ---") - # We might fail on playwright invoke if not installed, but let's check the route log/metadata - # To avoid full execution failure, we might mock tools too, but we want to see if `process_problem` calls them. - # Actually, let's just run it. If playwright fails, we'll see the error but we can check if it TRIED. - - try: - res_web = await orch.process_problem(text=query_web) - print(f"Result Status: {res_web['status']}") - print(f"Metadata Route: {res_web.get('metadata', {}).get('route')}") - # Check if tool context was seemingly added (via answer or logs) - # Since we don't return tool context directly in result, check logs or side effects? - # Actually, Orchestrator appends it to prompt, but doesn't return it in 'answer' directly unless Gemini uses it. - # But we logged "Executing Web Scraper..." - except Exception as e: - print(f"Web Route Execution Failed (Expected if browsers missing): {e}") - - # Test 2: Symbolic Route - query_sym = "integrate x^2" - print(f"\n--- Testing Symbolic Route: '{query_sym}' ---") - try: - res_sym = await orch.process_problem(text=query_sym) - print(f"Result Status: {res_sym['status']}") - print(f"Metadata Route: {res_sym.get('metadata', {}).get('route')}") - except Exception as e: - print(f"Symbolic Route Execution Failed: {e}") - -if __name__ == "__main__": - asyncio.run(test_routing()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_system_flow.py -================================================================================ - -import unittest -from unittest.mock import MagicMock, patch -import sys -import os - -# Add app to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from app.core.orchestrator import Orchestrator -from app.core.input_processor import InputType - -class TestSystemFlow(unittest.TestCase): - def setUp(self): - # Patch external dependencies BEFORE initializing Orchestrator - self.gemini_patcher = patch('app.core.orchestrator.GeminiSolver') - self.db_patcher = patch('app.core.orchestrator.DatabaseManager') - self.cache_patcher = patch('app.core.orchestrator.CacheManager') - - self.MockGemini = self.gemini_patcher.start() - self.MockDB = self.db_patcher.start() - self.MockCache = self.cache_patcher.start() - - # Setup Mock Instances - self.mock_solver = self.MockGemini.return_value - self.mock_db = self.MockDB.return_value - self.mock_cache = self.MockCache.return_value - - self.orchestrator = Orchestrator() - - def tearDown(self): - self.gemini_patcher.stop() - self.db_patcher.stop() - self.cache_patcher.stop() - - def test_full_flow_success(self): - """Test the compelte flow: Input -> Miss Cache -> Solve -> Save -> Return""" - - # 1. Setup Data - user_input = "Solve 2x + 4 = 10" - mock_solution = { - "latex": "2x + 4 = 10", - "reasoning": "2x = 6, x = 3", - "final_answer": "x = 3", - "confidence_score": 0.95 - } - - # 2. Configure Mocks - self.mock_cache.get_cached_answer.return_value = None # Cache Miss - self.mock_db.find_by_hash.return_value = None # DB Miss - self.mock_solver.solve.return_value = mock_solution # Solver Success - - # 3. Execution - result = self.orchestrator.process_problem(user_input) - - # 4. Assertions - self.assertEqual(result["status"], "success") - self.assertEqual(result["answer"], mock_solution) - self.assertEqual(result["metadata"]["source"], "generated") - - # Verify calls - self.mock_cache.get_cached_answer.assert_called_once() - self.mock_db.find_by_hash.assert_called_once() - self.mock_solver.solve.assert_called_once_with("solve 2x + 4 = 10") # Input is normalized - self.mock_db.save_problem.assert_called_once() - self.mock_cache.set_cached_answer.assert_called_once() - - def test_cache_hit(self): - """Test flow when answer is in cache.""" - user_input = "What is 2+2?" - cached_answer = { - "latex": "2+2", - "reasoning": "Simple addition", - "final_answer": "4", - "confidence_score": 1.0 - } - - self.mock_cache.get_cached_answer.return_value = cached_answer - - result = self.orchestrator.process_problem(user_input) - - self.assertEqual(result["status"], "success") - self.assertEqual(result["answer"], cached_answer) - self.assertEqual(result["metadata"]["source"], "cache") - - # Verify NO solve calls - self.mock_solver.solve.assert_not_called() - self.mock_db.save_problem.assert_not_called() - - def test_invalid_input(self): - """Test flow with invalid input.""" - user_input = "" # Empty input - - result = self.orchestrator.process_problem(user_input) - - self.assertEqual(result["status"], "error") - # Depending on input processor, it should return an error - self.assertIsNotNone(result["error"]) - - self.mock_solver.solve.assert_not_called() - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\test_vision_manual.py -================================================================================ - -import asyncio -import sys -import os -import base64 - -# Add project root to path -sys.path.append(os.getcwd()) - -print("Importing VisionAnalyzer... (This may take a moment)") -from app.tools.vision_analyzer import VisionAnalyzer -print("Import complete.") - -def test_vision(): - print("\n--- Test: Vision Analyzer (YOLO) ---") - - analyzer = VisionAnalyzer() - - # Find all image files - # extensions = {".jpg", ".jpeg", ".png", ".webp", ".bmp"} - # image_files = [f for f in os.listdir(".") if os.path.splitext(f)[1].lower() in extensions] - image_files = ["balls.jpg"] - - if not os.path.exists(image_files[0]): - print(f"Error: {image_files[0]} not found.") - return - - if not image_files: - print("No image files found in directory.") - return - - print(f"Found {len(image_files)} images: {image_files}") - - for test_img in image_files: - print(f"\nProcessing: {test_img}") - try: - with open(test_img, "rb") as f: - img_data = base64.b64encode(f.read()).decode("utf-8") - - query = "count the orbs" - result = analyzer.analyze(img_data, query) - - if result.get("status") == "success": - mode = result.get("vision_mode", "unknown") - print(f" Vision Mode: {mode}") - - if mode == "quantitative": - quant = result.get("quantitative_analysis", {}) - objects = quant.get("objects", {}) - print(f" Total Objects: {quant.get('total_objects')}") - print(f" Avg Confidence: {quant.get('avg_confidence')}") - if objects: - print(" Objects Detected:") - for key, count in objects.items(): - print(f" - {key}: {count}") - else: - print(" (No specific objects detected)") - else: - print(" Mode is qualitative (no YOLO run or no objects found).") - else: - print(" Error:", result.get("error")) - except Exception as e: - print(f" Failed to process {test_img}: {e}") - -if __name__ == "__main__": - test_vision() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\ui.py -================================================================================ - -import streamlit as st -from streamlit_drawable_canvas import st_canvas - -st.title("Canvas Debugger") -# Minimal possible canvas -canvas_result = st_canvas( - stroke_width=5, - stroke_color="#FF0000", - background_color="#000000", - height=400, - width=600, - drawing_mode="freedraw", - key="debug_canvas" -) -st.write("Is there image data?", canvas_result.image_data is not None) - -================================================================================ -FILE: e:\madhuri\mathminds\tests\verify_agent.py -================================================================================ - -import sys -import os -import asyncio -from unittest.mock import MagicMock, AsyncMock - -sys.path.append(os.getcwd()) - -# Mock generic modules -sys.modules['app.tools.vision_analyzer'] = MagicMock() -sys.modules['app.tools.web_scraper'] = MagicMock() -sys.modules['app.models.qwen'] = MagicMock() -# Mock ollama entirely to avoid qwen internal import lag if any -sys.modules['ollama'] = MagicMock() - -from app.core.orchestrator import Orchestrator - -async def verify_agent_loop(): - print("Initializing Orchestrator with mocks...") - - # Mock dependencies - mock_cache = MagicMock() - mock_cache.get_cached_answer.return_value = None - mock_db = MagicMock() - - orch = Orchestrator(cache_manager=mock_cache, db_manager=mock_db) - - # Mock Input Processor to return valid text - mock_processed = MagicMock() - mock_processed.is_valid = True - mock_processed.cleaned_content = "derivative of x^2" - mock_processed.metadata = {} - mock_processed.input_type.value = "text" - orch.input_processor.process_compound = MagicMock(return_value=mock_processed) - - # Mock Gemini - orch.gemini = MagicMock() - - # --- SIMULATE TOOL CALL RESPONSE --- - # Create a mock response object structure similar to google-genai SDK - mock_response_1 = MagicMock() - mock_response_1.text = "I should use the tool." - - # Mock Parts & FunctionCall - mock_info_part = MagicMock() - - mock_fc = MagicMock() - mock_fc.name = "solve_math_symbolically" - mock_fc.args = {"problem": "derivative of x^2"} - - mock_info_part.function_call = mock_fc - - # Candidates list - mock_candidate = MagicMock() - mock_candidate.content.parts = [mock_info_part] - mock_response_1.candidates = [mock_candidate] - - # set generate_with_tools return val - orch.gemini.generate_with_tools = AsyncMock(return_value=mock_response_1) - - # --- SIMULATE FINAL ANSWER RESPONSE --- - final_solution = { - "final_answer": "2x", - "reasoning": "Power rule", - "latex": "2x", - "confidence_score": 1.0 - } - orch.gemini.solve = AsyncMock(return_value=final_solution) - - # --- MOCK TOOL EXECUTION --- - # We want to see if the orchestrator actually calls the symbolic solver - # The Orchestrator's internal _get_tools creates closures calling self.symbolic_solver - orch.symbolic_solver.solve = MagicMock(return_value={"source": "sympy", "content": "2*x", "status": "success"}) - orch.math_normalizer.normalize = MagicMock(return_value=None) # Pass raw str to solver mock - - print("\nRunning Agent Loop...") - result = await orch.process_problem(text="derivative of x^2") - - print("\nOrchestrator Result:", result.get("status")) - - # Verify calls - print("\n--- Verification ---") - - # 1. Check if Gemini was called with tools - if orch.gemini.generate_with_tools.called: - print("✅ Gemini generate_with_tools called.") - else: - print("❌ Gemini generate_with_tools NOT called.") - - # 2. Check if Tool was executed (SymbolicSolver) - # The orchestrator should have called solve_math_symbolically -> symbolic_solver.solve - if orch.symbolic_solver.solve.called: - print(f"✅ SymbolicSolver called with: {orch.symbolic_solver.solve.call_args}") - else: - print("❌ SymbolicSolver NOT called.") - - # 3. Check final answer - if result["answer"] == final_solution: - print("✅ Final answer matches expected.") - else: - print(f"❌ Final answer mismatch: {result['answer']}") - -if __name__ == "__main__": - asyncio.run(verify_agent_loop()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\verify_error_codes.py -================================================================================ - -import sys -import os -import unittest -from unittest.mock import MagicMock -from fastapi.testclient import TestClient - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -from app.api.main import app -from app.api.deps import get_orchestrator -from app.core.errors import ErrorCodes, ERROR_MESSAGES - -class TestErrorSanitization(unittest.TestCase): - def setUp(self): - self.mock_orchestrator = MagicMock() - app.dependency_overrides[get_orchestrator] = lambda: self.mock_orchestrator - self.client = TestClient(app) - - def test_sanitized_error_response(self): - # Simulate Orchestrator returning an error with internal debug info - error_result = { - "status": "error", - "answer": None, - "error_code": ErrorCodes.INPUT_VALIDATION_ERROR, - "error_msg": ERROR_MESSAGES[ErrorCodes.INPUT_VALIDATION_ERROR], - "metadata": { - "request_id": "test-req-id", - "stage": "input_processing", - "_internal_debug": "Stack trace: ... sensitive info ..." - } - } - self.mock_orchestrator.process_problem.return_value = error_result - - response = self.client.post("/solve", json={"input": "bad input"}) - - # Assertions - self.assertEqual(response.status_code, 200) # We return 200 with status=error in body usually, or 400? - # Wait, main.py returns SolveResponse which is usually 200 unless exception raised. - # My implementation of process_problem returns a dict, main.py returns SolveResponse. - - data = response.json() - print(f"\nResponse Body: {data}") - - self.assertEqual(data["status"], "error") - self.assertEqual(data["error_code"], "ERR_002") # INPUT_VALIDATION_ERROR - self.assertEqual(data["error"], "The input provided is invalid.") - - # CRITICAL: Check sanitization - self.assertIn("metadata", data) - self.assertNotIn("_internal_debug", data["metadata"]) - self.assertIn("request_id", data["metadata"]) - - def tearDown(self): - app.dependency_overrides = {} - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\verify_ocr_logic.py -================================================================================ - -import sys -import os -import unittest -from unittest.mock import MagicMock, patch -from PIL import Image, ImageDraw -import io - -# Mock paddleocr in sys.modules to prevent real import attempt -# This avoids potential hangs if the library tries to download models or isn't installed -sys.modules['paddleocr'] = MagicMock() - -# Add app to path -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from app.core.ocr import OCRProcessor - -class TestOCRLogic(unittest.TestCase): - def test_preprocessing_converts_to_binary(self): - """Verify preprocessing steps produce a binary-like image.""" - ocr = OCRProcessor() - - # Create a test image (RGB, Red background with text) - img = Image.new('RGB', (100, 50), color = (255, 0, 0)) - d = ImageDraw.Draw(img) - d.text((10,10), "Test", fill=(0,0,0)) - - # Run preprocessing - processed = ocr._preprocess_image(img) - - # Check mode is '1' (binary) - # Note: Depending on environment, it might be 'L' if something failed, - # but my code explicitly does .point(..., '1') - self.assertEqual(processed.mode, '1', "Image should be converted to binary mode '1'") - print("\n[PASS] Preprocessing converted image to binary mode.") - - def test_structure_preservation(self): - """Verify that multi-line text is joined with newlines.""" - ocr = OCRProcessor() - - # Mock the internal OCR engine - mock_ocr_engine = MagicMock() - # Mock result structure: [ [ [box], ("Text", score) ], ... ] wrapped in outer list - # PaddleOCR returns a list of results (one per image), we perform on 1 image, so result[0] is the list of lines. - mock_lines = [ - (None, ("Line 1", 0.99)), - (None, ("Line 2", 0.98)), - (None, ("Line 3", 0.97)) - ] - mock_ocr_engine.ocr.return_value = [mock_lines] - - # Inject mock - ocr.ocr = mock_ocr_engine - ocr._enabled = True - - # Create dummy image bytes - img = Image.new('RGB', (100, 100)) - buf = io.BytesIO() - img.save(buf, format='PNG') - img_bytes = buf.getvalue() - - # Run process - result = ocr._process_image_data(img_bytes) - - expected = "Line 1\nLine 2\nLine 3" - self.assertEqual(result, expected, "Result text should preserve newlines") - print(f"\n[PASS] Structure preservation verified. Output:\n{result}") - -if __name__ == '__main__': - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\verify_orchestrator_math.py -================================================================================ - -import sys -import os -import asyncio -from unittest.mock import MagicMock - -sys.path.append(os.getcwd()) - -# Mock modules BEFORE importing Orchestrator -sys.modules['app.models.gemini'] = MagicMock() -sys.modules['app.models.qwen'] = MagicMock() -sys.modules['app.tools.vision_analyzer'] = MagicMock() # Mock entire module - -# Mocking VisionAnalyzer class specifically in the module mock -mock_vision_module = sys.modules['app.tools.vision_analyzer'] -mock_vision_module.VisionAnalyzer = MagicMock() - -from app.core.orchestrator import Orchestrator - -async def verify(): - print("Initializing Orchestrator with mocks...") - - # Mock dependencies - mock_cache = MagicMock() - mock_cache.get_cached_answer.return_value = None # Force cache MISS - mock_db = MagicMock() - - # We need real logic for Orchestrator, but mocks for heavy lifting - orch = Orchestrator(cache_manager=mock_cache, db_manager=mock_db) - - # Ensure symbolic solver is real (it should be, unless we mocked app.tools.symbolic_solver) - # math_normalizer should be real too. - - query = "derivative of tan(x)" - print(f"\nProcessing query: '{query}'") - - # We need to mock InputProcessor's return value to bypass complexity there - # But we want the text to flow through - mock_processed = MagicMock() - mock_processed.is_valid = True - mock_processed.cleaned_content = query - mock_processed.metadata = {} - mock_processed.input_type.value = "text" - - orch.input_processor.process_compound = MagicMock(return_value=mock_processed) - - # Run - # Note: we pass text=query but input_processor mock will control what's used - result = await orch.process_problem(text=query) - - print("\nOrchestrator Result Status:", result.get("status")) - - if result.get("status") == "success": - ans = result.get("answer", {}) - print("Answer Keys:", list(ans.keys())) - - required = ["final_answer", "latex", "confidence_score", "reasoning"] - missing = [k for k in required if k not in ans] - - if not missing: - print("✅ SUCCESS: All required keys present.") - print(f" Final Answer: {ans['final_answer']}") - else: - print(f"❌ FAIL: Missing keys: {missing}") - print(f" Actual Answer Dict: {ans}") - - else: - print(f"❌ FAIL: Process failed with error: {result.get('error_msg')}") - print(f" Debug: {result.get('metadata', {}).get('debug_error')}") - -if __name__ == "__main__": - asyncio.run(verify()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\verify_pooling.py -================================================================================ - - -import unittest -import redis -import pymongo -from app.api.deps import get_orchestrator, get_redis_pool, get_mongo_client - -class TestConnectionPooling(unittest.TestCase): - - def test_shared_pools(self): - """Test that dependency provider returns shared pools.""" - pool1 = get_redis_pool() - pool2 = get_redis_pool() - self.assertIs(pool1, pool2, "Redis pool should be a singleton") - - client1 = get_mongo_client() - client2 = get_mongo_client() - self.assertIs(client1, client2, "Mongo client should be a singleton") - - def test_orchestrator_shares_pools(self): - """Test that Orchestrator uses the shared pools.""" - orch1 = get_orchestrator() - orch2 = get_orchestrator() - - # Check Orchestrator singleton - self.assertIs(orch1, orch2) - - # Check that orchestrator components use the shared pool - shared_redis_pool = get_redis_pool() - shared_mongo_client = get_mongo_client() - - # Depends on internal structure: - # orch.cache_manager.redis_client.connection_pool - # orch.db_manager.client - - if orch1.cache_manager.redis_client: - self.assertIs( - orch1.cache_manager.redis_client.connection_pool, - shared_redis_pool, - "CacheManager should use the shared Redis connection pool" - ) - - if orch1.db_manager.client: - self.assertIs( - orch1.db_manager.client, - shared_mongo_client, - "DatabaseManager should use the shared Mongo client" - ) - -if __name__ == "__main__": - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\verify_reliability.py -================================================================================ - - -import unittest -import json -import logging -import uuid -# Mock components -from unittest.mock import MagicMock, patch - -from app.reasoning.gemini_client import GeminiSolver -from app.core.orchestrator import Orchestrator -from app.api.deps import get_orchestrator - -class TestReliability(unittest.TestCase): - - def setUp(self): - logging.basicConfig(level=logging.ERROR) - - def test_safe_parse_json(self): - """Test the JSON repair logic in GeminiSolver.""" - solver = GeminiSolver(api_key="fake") - - # Test 1: Valid JSON - valid = '{"key": "value"}' - self.assertEqual(solver._safe_parse_json(valid), {"key": "value"}) - - # Test 2: Wrapped in markdown - wrapped = '```json\n{"key": "value"}\n```' - self.assertEqual(solver._safe_parse_json(wrapped), {"key": "value"}) - - # Test 3: Surrounded by text - surrounded = 'Here is the json: {"key": "value"} thanks.' - self.assertEqual(solver._safe_parse_json(surrounded), {"key": "value"}) - - # Test 4: Invalid JSON - invalid = '{key: value}' # Missing quotes - self.assertIsNone(solver._safe_parse_json(invalid)) - - @patch("app.core.orchestrator.GeminiSolver") - @patch("app.core.orchestrator.CacheManager") - @patch("app.core.orchestrator.DatabaseManager") - def test_orchestrator_error_metadata(self, mock_db, mock_cache, mock_solver): - """Test that Orchestrator returns rich error metadata.""" - - # Setup mocks - mock_solver_inst = mock_solver.return_value - mock_solver_inst.solve.side_effect = Exception("Failed to parse JSON") - - # Ensure cache miss - mock_cache.return_value.get_cached_answer.return_value = None - mock_db.return_value.find_by_hash.return_value = None - - orchestrator = Orchestrator() - - # Call process_problem - req_id = "test-uuid-123" - result = orchestrator.process_problem("test input", request_id=req_id) - - # Verify structure - self.assertEqual(result["status"], "error") - self.assertIn("metadata", result) - self.assertEqual(result["metadata"]["request_id"], req_id) - self.assertEqual(result["metadata"]["stage"], "reasoning") - self.assertIn("error_detail", result["metadata"]) - - def test_dependency_injection(self): - """Test that the dependency provider works.""" - orch = get_orchestrator() - self.assertIsInstance(orch, Orchestrator) - - # Verify singleton behavior (lru_cache) - orch2 = get_orchestrator() - self.assertIs(orch, orch2) - -if __name__ == "__main__": - unittest.main() - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\verify_routing.py -================================================================================ - -import asyncio -import sys -import os - -# Add project root to path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from app.core.orchestrator import Orchestrator, IntentType - -async def test_routing(): - print("Initializing Orchestrator...") - orchestrator = Orchestrator() - - test_cases = [ - ("2 + 2", IntentType.ARITHMETIC), - ("solve x^2 + 2x + 1 = 0", IntentType.SYMBOLIC_MATH), - ("price of bitcoin", IntentType.SEARCH), - ("explain quantum physics", IntentType.CONCEPTUAL), - ("", IntentType.UNKNOWN) - ] - - print("\n--- Testing Classification Logic ---") - for text, expected in test_cases: - intent = orchestrator._classify_intent(text, has_image=False) - status = "✅" if intent == expected else f"❌ (Got {intent})" - print(f"Input: '{text}' -> {expected.value} : {status}") - - print("\n--- Testing Execution (Dry Run) ---") - # We won't run full execution to avoid API costs/time, just check classification for now - # But let's run one simple arithmetic to see the full flow - - print("Running full flow for '50 + 50'...") - try: - result = await orchestrator.process_problem(text="50 + 50") - print(f"Result Status: {result['status']}") - print(f"Source: {result['source']}") - print(f"Answer: {result['answer']}") - - if result['source'] == "symbolic_solver" and result['answer'] == "100": - print("✅ Arithmetic Routing Success") - else: - print("❌ Arithmetic Routing Failed") - - except Exception as e: - print(f"Execution failed: {e}") - -if __name__ == "__main__": - asyncio.run(test_routing()) - - -================================================================================ -FILE: e:\madhuri\mathminds\tests\__init__.py -================================================================================ - -# Tests placeholder -