Skip to main content
Glama
__init__.py28.3 kB
""" AI Providers Package Provides AI provider implementations with lazy loading and dependency management. Includes automatic dependency installation and enhanced error handling. """ import subprocess import traceback from typing import Any, Dict import FreeCAD # Import base classes first from ai.providers.base_provider import ( AIMessage, AIResponse, BaseAIProvider, MessageRole, ) # Lazy loading for providers to handle missing dependencies gracefully _providers = {} _provider_errors = {} _dependency_install_attempted = False def _attempt_dependency_installation(): """Attempt to install missing dependencies automatically.""" global _dependency_install_attempted if _dependency_install_attempted: return False # Don't try multiple times _dependency_install_attempted = True try: FreeCAD.Console.PrintMessage( "FreeCAD AI: Attempting automatic dependency installation...\n" ) # Try to import and use the dependency manager try: from utils.dependency_manager import DependencyManager def progress_callback(message): FreeCAD.Console.PrintMessage(f"FreeCAD AI: {message}\n") manager = DependencyManager(progress_callback) # Check for missing critical dependencies and sub-dependencies critical_missing = manager.get_critical_missing_dependencies() if critical_missing: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Installing critical dependencies: {', '.join(critical_missing)}\n" ) # Special handling for aiohttp and its sub-dependencies if "aiohttp" in critical_missing: FreeCAD.Console.PrintMessage( "FreeCAD AI: Checking aiohttp sub-dependencies...\n" ) # Check sub-dependencies sub_deps = manager.check_sub_dependencies("aiohttp") missing_sub_deps = [ dep for dep, available in sub_deps.items() if not available ] if missing_sub_deps: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Missing sub-dependencies: {', '.join(missing_sub_deps)}\n" ) # Try to install critical dependencies with sub-dependency support success = manager.install_missing_dependencies(critical_only=True) if success: FreeCAD.Console.PrintMessage( "FreeCAD AI: ✅ Critical dependencies installed successfully\n" ) # Verify sub-dependencies were installed correctly if "aiohttp" in critical_missing and manager.check_dependency( "aiohttp" ): sub_deps_after = manager.check_sub_dependencies("aiohttp") still_missing = [ dep for dep, available in sub_deps_after.items() if not available ] if still_missing: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ⚠️ Some sub-dependencies still missing: {', '.join(still_missing)}\n" ) FreeCAD.Console.PrintMessage( "FreeCAD AI: Attempting to install missing sub-dependencies individually...\n" ) # Try to install missing sub-dependencies individually for sub_dep in still_missing: if manager.install_dependency(sub_dep): FreeCAD.Console.PrintMessage( f"FreeCAD AI: ✅ Sub-dependency {sub_dep} installed\n" ) else: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ❌ Failed to install sub-dependency {sub_dep}\n" ) else: FreeCAD.Console.PrintMessage( "FreeCAD AI: ✅ All aiohttp sub-dependencies verified\n" ) FreeCAD.Console.PrintMessage( "FreeCAD AI: Please restart FreeCAD to use the new dependencies\n" ) return True else: FreeCAD.Console.PrintWarning( "FreeCAD AI: ❌ Failed to install some critical dependencies\n" ) return False else: FreeCAD.Console.PrintMessage( "FreeCAD AI: All critical dependencies are already available\n" ) # Even if main dependencies are available, check sub-dependencies if manager.check_dependency("aiohttp"): sub_deps = manager.check_sub_dependencies("aiohttp") missing_sub_deps = [ dep for dep, available in sub_deps.items() if not available ] if missing_sub_deps: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Installing missing aiohttp sub-dependencies: {', '.join(missing_sub_deps)}\n" ) for sub_dep in missing_sub_deps: if manager.install_dependency(sub_dep): FreeCAD.Console.PrintMessage( f"FreeCAD AI: ✅ Sub-dependency {sub_dep} installed\n" ) else: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ❌ Failed to install sub-dependency {sub_dep}\n" ) return True except ImportError as e: FreeCAD.Console.PrintWarning( f"FreeCAD AI: Could not import dependency manager: {e}\n" ) return False except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI: Dependency installation failed: {e}\n" ) return False except Exception as e: FreeCAD.Console.PrintError( f"FreeCAD AI: Automatic dependency installation error: {e}\n" ) return False def _show_dependency_guidance(missing_dependency, provider_name): """Show user-friendly guidance for installing missing dependencies.""" FreeCAD.Console.PrintMessage("=" * 60 + "\n") FreeCAD.Console.PrintMessage("FreeCAD AI: DEPENDENCY INSTALLATION GUIDE\n") FreeCAD.Console.PrintMessage("=" * 60 + "\n") if missing_dependency == "aiohttp": FreeCAD.Console.PrintMessage( f"The {provider_name} provider requires the 'aiohttp' library.\n" ) FreeCAD.Console.PrintMessage("\n📋 INSTALLATION OPTIONS:\n") FreeCAD.Console.PrintMessage("\n1. 🔧 Use the FreeCAD AI Dependencies Tab:\n") FreeCAD.Console.PrintMessage(" - Open the FreeCAD AI interface\n") FreeCAD.Console.PrintMessage( " - Go to the 'Dependencies' or 'Settings' tab\n" ) FreeCAD.Console.PrintMessage(" - Click 'Install Missing Dependencies'\n") FreeCAD.Console.PrintMessage( "\n2. 🐍 Manual Installation via FreeCAD Python Console:\n" ) FreeCAD.Console.PrintMessage( " Copy and paste this into the FreeCAD Python console:\n" ) FreeCAD.Console.PrintMessage(" " + "-" * 50 + "\n") # Generate installation script try: from utils.dependency_manager import DependencyManager manager = DependencyManager() script = manager.create_install_script("aiohttp") # Show just the key parts FreeCAD.Console.PrintMessage(" exec('''") for line in script.split("\n")[10:25]: # Show relevant parts if line.strip() and not line.startswith("#"): FreeCAD.Console.PrintMessage(f" {line}\n") FreeCAD.Console.PrintMessage(" ''')\n") except (AttributeError, IndexError, TypeError): # AttributeError: Script access issues # IndexError: Script parsing issues # TypeError: Script format issues # Fallback simple script FreeCAD.Console.PrintMessage(" import subprocess, sys, os\n") FreeCAD.Console.PrintMessage( " subprocess.run([sys.executable, '-m', 'pip', 'install', 'aiohttp>=3.8.0'])\n" ) FreeCAD.Console.PrintMessage(" " + "-" * 50 + "\n") FreeCAD.Console.PrintMessage("\n3. 💻 System Package Manager:\n") FreeCAD.Console.PrintMessage( " Ubuntu/Debian: sudo apt install python3-aiohttp\n" ) FreeCAD.Console.PrintMessage(" Fedora: sudo dnf install python3-aiohttp\n") FreeCAD.Console.PrintMessage(" macOS: pip3 install aiohttp\n") FreeCAD.Console.PrintMessage("\n4. 🔄 Restart FreeCAD:\n") FreeCAD.Console.PrintMessage( " After installation, restart FreeCAD to use the new dependency\n" ) else: FreeCAD.Console.PrintMessage( f"The {provider_name} provider requires the '{missing_dependency}' library.\n" ) FreeCAD.Console.PrintMessage( f"Please install it using: pip install {missing_dependency}\n" ) FreeCAD.Console.PrintMessage( "Then restart FreeCAD to use the new dependency.\n" ) FreeCAD.Console.PrintMessage("=" * 60 + "\n") def _show_sub_dependency_guidance( main_dependency, missing_sub_dependency, provider_name ): """Show user-friendly guidance for installing missing sub-dependencies.""" FreeCAD.Console.PrintMessage("=" * 60 + "\n") FreeCAD.Console.PrintMessage("FreeCAD AI: SUB-DEPENDENCY INSTALLATION GUIDE\n") FreeCAD.Console.PrintMessage("=" * 60 + "\n") FreeCAD.Console.PrintMessage( f"The {provider_name} provider requires '{missing_sub_dependency}', which is a sub-dependency of '{main_dependency}'.\n" ) FreeCAD.Console.PrintMessage( f"This usually means {main_dependency} was installed without its required sub-dependencies.\n" ) FreeCAD.Console.PrintMessage("\n📋 INSTALLATION OPTIONS:\n") FreeCAD.Console.PrintMessage("\n1. 🔧 Use the FreeCAD AI Dependencies Tab:\n") FreeCAD.Console.PrintMessage(" - Open the FreeCAD AI interface\n") FreeCAD.Console.PrintMessage(" - Go to the 'Dependencies' or 'Settings' tab\n") FreeCAD.Console.PrintMessage(" - Click 'Install Missing Dependencies'\n") FreeCAD.Console.PrintMessage( "\n2. 🐍 Manual Installation via FreeCAD Python Console:\n" ) FreeCAD.Console.PrintMessage( " Copy and paste this into the FreeCAD Python console:\n" ) FreeCAD.Console.PrintMessage(" " + "-" * 50 + "\n") # Generate installation script for sub-dependency try: from utils.dependency_manager import DependencyManager manager = DependencyManager() # Create script for both main dependency and sub-dependency FreeCAD.Console.PrintMessage(" exec('''\n") FreeCAD.Console.PrintMessage(" import subprocess, sys, os\n") FreeCAD.Console.PrintMessage(" \n") FreeCAD.Console.PrintMessage(" # Install sub-dependency first\n") FreeCAD.Console.PrintMessage( f" subprocess.run([sys.executable, '-m', 'pip', 'install', '{missing_sub_dependency}'])\n" ) FreeCAD.Console.PrintMessage(" \n") FreeCAD.Console.PrintMessage( " # Reinstall main dependency to ensure compatibility\n" ) FreeCAD.Console.PrintMessage( f" subprocess.run([sys.executable, '-m', 'pip', 'install', '--upgrade', '{main_dependency}'])\n" ) FreeCAD.Console.PrintMessage(" ''')\n") except (AttributeError, KeyError, TypeError): # AttributeError: Missing dependency manager attributes # KeyError: Missing dependency information # TypeError: Invalid dependency data format # Fallback simple script FreeCAD.Console.PrintMessage(" import subprocess, sys\n") FreeCAD.Console.PrintMessage( f" subprocess.run([sys.executable, '-m', 'pip', 'install', '{missing_sub_dependency}'])\n" ) FreeCAD.Console.PrintMessage( f" subprocess.run([sys.executable, '-m', 'pip', 'install', '--upgrade', '{main_dependency}'])\n" ) FreeCAD.Console.PrintMessage(" " + "-" * 50 + "\n") FreeCAD.Console.PrintMessage("\n3. 💻 System Package Manager:\n") if main_dependency == "aiohttp": FreeCAD.Console.PrintMessage( " Ubuntu/Debian: sudo apt install python3-aiohttp python3-multidict python3-yarl\n" ) FreeCAD.Console.PrintMessage( " Fedora: sudo dnf install python3-aiohttp python3-multidict python3-yarl\n" ) FreeCAD.Console.PrintMessage(" macOS: pip3 install aiohttp[speedups]\n") else: FreeCAD.Console.PrintMessage( f" Install both: pip3 install {missing_sub_dependency} {main_dependency}\n" ) FreeCAD.Console.PrintMessage("\n4. 🔄 Restart FreeCAD:\n") FreeCAD.Console.PrintMessage( " After installation, restart FreeCAD to use the new dependencies\n" ) FreeCAD.Console.PrintMessage("\n💡 TROUBLESHOOTING:\n") FreeCAD.Console.PrintMessage( " - Sub-dependencies are often missed when using --no-deps flag\n" ) FreeCAD.Console.PrintMessage( " - Try reinstalling the main dependency without --no-deps\n" ) FreeCAD.Console.PrintMessage(" - Check that your pip version is up to date\n") FreeCAD.Console.PrintMessage("=" * 60 + "\n") def _lazy_import_provider(provider_name: str, module_name: str, class_name: str): """Lazy import a provider with enhanced dependency management and error handling.""" if provider_name in _providers: return _providers[provider_name] if provider_name in _provider_errors: # Don't retry if we already failed, unless dependencies were installed return None try: module = __import__(f"ai.providers.{module_name}", fromlist=[class_name]) provider_class = getattr(module, class_name) _providers[provider_name] = provider_class FreeCAD.Console.PrintMessage( f"FreeCAD AI: ✅ {provider_name} provider loaded successfully\n" ) return provider_class except ImportError as e: error_msg = f"Failed to import {provider_name}: {str(e)}" _provider_errors[provider_name] = error_msg # Enhanced sub-dependency detection missing_dependency = None missing_sub_dependency = None if "aiohttp" in str(e): missing_dependency = "aiohttp" elif "multidict" in str(e): missing_dependency = "aiohttp" missing_sub_dependency = "multidict" elif "yarl" in str(e): missing_dependency = "aiohttp" missing_sub_dependency = "yarl" elif "aiosignal" in str(e): missing_dependency = "aiohttp" missing_sub_dependency = "aiosignal" elif "requests" in str(e): missing_dependency = "requests" elif "mcp" in str(e): missing_dependency = "mcp" if missing_dependency: if missing_sub_dependency: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ❌ {provider_name} provider unavailable - missing '{missing_sub_dependency}' sub-dependency\n" ) FreeCAD.Console.PrintWarning( f"FreeCAD AI: This is a sub-dependency of '{missing_dependency}' that wasn't installed properly\n" ) else: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ❌ {provider_name} provider unavailable - missing '{missing_dependency}' dependency\n" ) # Attempt automatic installation with sub-dependency handling if not _dependency_install_attempted: if missing_sub_dependency: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Attempting to install {missing_dependency} with sub-dependencies...\n" ) else: FreeCAD.Console.PrintMessage( f"FreeCAD AI: Attempting to install {missing_dependency} automatically...\n" ) if _attempt_dependency_installation(): FreeCAD.Console.PrintMessage( f"FreeCAD AI: ✅ Dependencies installed - restart FreeCAD to use {provider_name}\n" ) else: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ❌ Automatic installation failed\n" ) if missing_sub_dependency: _show_sub_dependency_guidance( missing_dependency, missing_sub_dependency, provider_name ) else: _show_dependency_guidance(missing_dependency, provider_name) else: if missing_sub_dependency: _show_sub_dependency_guidance( missing_dependency, missing_sub_dependency, provider_name ) else: _show_dependency_guidance(missing_dependency, provider_name) else: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ❌ {provider_name} provider import failed: {error_msg}\n" ) return None except Exception as e: error_msg = f"Error loading {provider_name}: {str(e)}" _provider_errors[provider_name] = error_msg FreeCAD.Console.PrintError( f"FreeCAD AI: ❌ {provider_name} provider error: {error_msg}\n" ) FreeCAD.Console.PrintError( f"FreeCAD AI: {provider_name} traceback: {traceback.format_exc()}\n" ) return None def get_claude_provider(): """Get Claude provider class with lazy loading and dependency management.""" return _lazy_import_provider("Claude", "claude_provider", "ClaudeProvider") def get_gemini_provider(): """Get Gemini provider class with lazy loading and dependency management.""" return _lazy_import_provider("Gemini", "gemini_provider", "GeminiProvider") def get_vertexai_provider(): """Get Vertex AI provider class with lazy loading and dependency management.""" return _lazy_import_provider("VertexAI", "vertexai_provider", "VertexAIProvider") def get_openrouter_provider(): """Get OpenRouter provider class with lazy loading and dependency management.""" return _lazy_import_provider( "OpenRouter", "openrouter_provider", "OpenRouterProvider" ) def get_available_providers() -> Dict[str, Any]: """Get all available provider classes. Returns: Dictionary mapping provider names to classes (None if unavailable) """ providers = { "Claude": get_claude_provider(), "Gemini": get_gemini_provider(), "VertexAI": get_vertexai_provider(), "OpenRouter": get_openrouter_provider(), } # Enhanced availability summary with sub-dependency information available_count = sum(1 for p in providers.values() if p is not None) total_count = len(providers) if available_count == 0: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ⚠️ No AI providers available ({available_count}/{total_count})\n" ) FreeCAD.Console.PrintWarning( "FreeCAD AI: Install missing dependencies to enable AI providers\n" ) # Check if it's a sub-dependency issue try: from utils.dependency_manager import DependencyManager manager = DependencyManager() if manager.check_dependency("aiohttp"): sub_deps = manager.check_sub_dependencies("aiohttp") missing_sub_deps = [ dep for dep, available in sub_deps.items() if not available ] if missing_sub_deps: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ⚠️ aiohttp is installed but missing sub-dependencies: {', '.join(missing_sub_deps)}\n" ) FreeCAD.Console.PrintWarning( "FreeCAD AI: Use Dependencies tab to fix sub-dependency issues\n" ) except (ImportError, AttributeError): # ImportError: Missing dependency modules for checking # AttributeError: Missing expected dependency attributes pass # Ignore errors in sub-dependency checking elif available_count < total_count: FreeCAD.Console.PrintMessage( f"FreeCAD AI: ⚠️ Partial AI provider availability ({available_count}/{total_count})\n" ) # Show which providers are missing and why for provider_name, provider_class in providers.items(): if provider_class is None and provider_name in _provider_errors: error = _provider_errors[provider_name] if "multidict" in error or "yarl" in error or "aiosignal" in error: FreeCAD.Console.PrintMessage( f"FreeCAD AI: ⚠️ {provider_name}: Missing sub-dependency\n" ) else: FreeCAD.Console.PrintMessage( f"FreeCAD AI: ⚠️ {provider_name}: Missing main dependency\n" ) else: FreeCAD.Console.PrintMessage( f"FreeCAD AI: ✅ All AI providers available ({available_count}/{total_count})\n" ) return providers def get_provider_errors() -> Dict[str, str]: """Get any provider loading errors. Returns: Dictionary mapping provider names to error messages """ return _provider_errors.copy() def check_dependencies() -> Dict[str, bool]: """Check if dependencies are available for providers with enhanced reporting. Returns: Dictionary mapping dependency names to availability """ dependencies = {} try: # Try to use the enhanced dependency manager from utils.dependency_manager import check_dependencies as enhanced_check dependencies = enhanced_check() # Log dependency status FreeCAD.Console.PrintMessage("FreeCAD AI: Dependency Status:\n") for dep_name, available in dependencies.items(): status = "✅" if available else "❌" FreeCAD.Console.PrintMessage( f" {status} {dep_name}: {'Available' if available else 'Missing'}\n" ) return dependencies except ImportError: FreeCAD.Console.PrintMessage("FreeCAD AI: Using fallback dependency check\n") # Fallback manual check test_dependencies = ["aiohttp", "requests", "mcp"] for dep_name in test_dependencies: try: __import__(dep_name) dependencies[dep_name] = True FreeCAD.Console.PrintMessage(f"FreeCAD AI: ✅ {dep_name}: Available\n") except ImportError: dependencies[dep_name] = False FreeCAD.Console.PrintMessage(f"FreeCAD AI: ❌ {dep_name}: Missing\n") return dependencies def install_missing_dependencies(): """Attempt to install missing dependencies with user guidance.""" try: FreeCAD.Console.PrintMessage( "FreeCAD AI: Checking and installing missing dependencies...\n" ) # Try to use the dependency manager from utils.dependency_manager import auto_install_dependencies def progress_callback(message): FreeCAD.Console.PrintMessage(f"FreeCAD AI: {message}\n") success = auto_install_dependencies(progress_callback) if success: FreeCAD.Console.PrintMessage( "FreeCAD AI: ✅ Dependencies installed successfully\n" ) FreeCAD.Console.PrintMessage( "FreeCAD AI: 🔄 Please restart FreeCAD to use the new dependencies\n" ) return True else: FreeCAD.Console.PrintWarning( "FreeCAD AI: ❌ Some dependencies failed to install\n" ) FreeCAD.Console.PrintWarning( "FreeCAD AI: See the installation guide above for manual installation\n" ) return False except ImportError as e: FreeCAD.Console.PrintError( f"FreeCAD AI: Could not import dependency manager: {e}\n" ) FreeCAD.Console.PrintMessage( "FreeCAD AI: Please install dependencies manually\n" ) return False except (subprocess.CalledProcessError, PermissionError, FileNotFoundError) as e: # subprocess.CalledProcessError: Installation command failed # PermissionError: Insufficient permissions for installation # FileNotFoundError: pip or python executable not found FreeCAD.Console.PrintError(f"FreeCAD AI: Dependency installation error: {e}\n") return False # For backward compatibility, try to import providers directly # but don't fail if dependencies are missing ClaudeProvider = get_claude_provider() GeminiProvider = get_gemini_provider() VertexAIProvider = get_vertexai_provider() OpenRouterProvider = get_openrouter_provider() # Import MCP integrated provider directly (it has no external dependencies) try: from ai.providers.mcp_integrated_provider import MCPIntegratedProvider FreeCAD.Console.PrintMessage( "FreeCAD AI: ✅ MCPIntegratedProvider loaded successfully\n" ) except ImportError as e: FreeCAD.Console.PrintWarning( f"FreeCAD AI: ❌ Failed to import MCPIntegratedProvider: {e}\n" ) MCPIntegratedProvider = None # Export list for explicit imports __all__ = [ "BaseAIProvider", "AIResponse", "AIMessage", "MessageRole", "get_claude_provider", "get_gemini_provider", "get_vertexai_provider", "get_openrouter_provider", "get_available_providers", "get_provider_errors", "check_dependencies", "install_missing_dependencies", "MCPIntegratedProvider", ] # Add provider classes if they loaded successfully if ClaudeProvider: __all__.append("ClaudeProvider") if GeminiProvider: __all__.append("GeminiProvider") if OpenRouterProvider: __all__.append("OpenRouterProvider") # Show initial status try: available_providers = get_available_providers() dependency_status = check_dependencies() # Show summary provider_count = sum(1 for p in available_providers.values() if p is not None) dependency_count = sum(1 for d in dependency_status.values() if d) if provider_count == 0: FreeCAD.Console.PrintMessage( "FreeCAD AI: 💡 To enable AI providers, install missing dependencies using the Dependencies tab\n" ) except (ImportError, AttributeError, NameError) as e: # ImportError: Missing modules during provider initialization # AttributeError: Missing expected provider attributes # NameError: Missing variables during initialization FreeCAD.Console.PrintError( f"FreeCAD AI: Error during provider initialization: {e}\n" )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/mcp-freecad'

If you have feedback or need assistance with the MCP directory API, please join our Discord server