Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """ | |
| OpenManus Integration Fixer | |
| This script fixes common issues with OpenManus installations and ensures | |
| proper integration with Casibase and other Atlas components. | |
| """ | |
| import os | |
| import sys | |
| import json | |
| import shutil | |
| import subprocess | |
| import importlib | |
| import logging | |
| from pathlib import Path | |
| from typing import Dict, List, Optional, Tuple, Any | |
| # Configure logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s', | |
| handlers=[ | |
| logging.StreamHandler(sys.stdout), | |
| logging.FileHandler('openmanus_fix.log') | |
| ] | |
| ) | |
| logger = logging.getLogger(__name__) | |
| class OpenManusFixer: | |
| """Utility class to fix OpenManus integration issues.""" | |
| def __init__(self, openmanus_path: Optional[str] = None): | |
| """Initialize the fixer with the path to OpenManus.""" | |
| if openmanus_path: | |
| self.openmanus_path = Path(openmanus_path) | |
| else: | |
| # Try to find OpenManus in the current directory or parent | |
| current_dir = Path.cwd() | |
| if (current_dir / "app").exists() and (current_dir / "requirements.txt").exists(): | |
| self.openmanus_path = current_dir | |
| else: | |
| self.openmanus_path = Path(os.path.expanduser("~/OpenManus")) | |
| logger.info(f"OpenManus path: {self.openmanus_path}") | |
| if not self.openmanus_path.exists(): | |
| raise FileNotFoundError(f"OpenManus directory not found at {self.openmanus_path}") | |
| def fix_requirements(self) -> None: | |
| """Fix the requirements.txt file to ensure all dependencies are properly listed.""" | |
| req_file = self.openmanus_path / "requirements.txt" | |
| if not req_file.exists(): | |
| logger.error(f"Requirements file not found at {req_file}") | |
| return | |
| logger.info("Fixing requirements.txt...") | |
| # Read existing requirements | |
| with open(req_file, "r") as f: | |
| requirements = f.readlines() | |
| # Clean requirements by removing comments and invalid entries | |
| cleaned_requirements = [] | |
| for req in requirements: | |
| req = req.strip() | |
| # Skip empty lines and comments | |
| if not req or req.startswith('#'): | |
| cleaned_requirements.append(req) | |
| continue | |
| # Fix any malformed requirements | |
| if ' #' in req: | |
| req = req.split(' #')[0].strip() | |
| cleaned_requirements.append(req) | |
| # Add missing dependencies | |
| required_deps = { | |
| "flask": "flask~=2.3.3", | |
| "flask-cors": "flask-cors~=4.0.0", | |
| "requests": "requests~=2.31.0", | |
| "pydantic": "pydantic~=2.10.6", | |
| "openai": "openai~=1.66.3", | |
| "fastapi": "fastapi~=0.115.11", | |
| "uvicorn": "uvicorn~=0.34.0", | |
| "httpx": "httpx>=0.27.0", | |
| "tiktoken": "tiktoken~=0.9.0" | |
| } | |
| # Check for each required dependency | |
| for dep_name, dep_req in required_deps.items(): | |
| found = False | |
| for i, req in enumerate(cleaned_requirements): | |
| if req.startswith(f"{dep_name}"): | |
| found = True | |
| # Update the dependency if needed | |
| cleaned_requirements[i] = dep_req | |
| break | |
| if not found: | |
| cleaned_requirements.append(dep_req) | |
| # Write back the fixed requirements | |
| with open(req_file, "w") as f: | |
| f.write("\n".join(cleaned_requirements) + "\n") | |
| logger.info("Requirements file fixed") | |
| def install_dependencies(self) -> None: | |
| """Install or upgrade required dependencies.""" | |
| logger.info("Installing/upgrading dependencies...") | |
| try: | |
| subprocess.run([ | |
| sys.executable, "-m", "pip", "install", "-r", | |
| str(self.openmanus_path / "requirements.txt"), | |
| "--upgrade" | |
| ], check=True) | |
| logger.info("Dependencies installed successfully") | |
| except subprocess.CalledProcessError as e: | |
| logger.error(f"Failed to install dependencies: {e}") | |
| def ensure_casibase_connector(self) -> None: | |
| """Ensure that the Casibase connector exists and is properly configured.""" | |
| connector_path = self.openmanus_path / "app" / "casibase_connector.py" | |
| if not connector_path.exists(): | |
| logger.info("Creating Casibase connector...") | |
| # Check if the directory exists | |
| connector_path.parent.mkdir(exist_ok=True) | |
| # Create the connector file | |
| self.create_casibase_connector(connector_path) | |
| else: | |
| logger.info("Updating existing Casibase connector...") | |
| self.create_casibase_connector(connector_path) | |
| logger.info("Casibase connector updated") | |
| def create_casibase_connector(self, path: Path) -> None: | |
| """Create or update the Casibase connector file.""" | |
| connector_code = """ | |
| \"\"\" | |
| Casibase Connector for OpenManus | |
| This module provides integration with Casibase for document storage, retrieval, and querying. | |
| \"\"\" | |
| import os | |
| import json | |
| import requests | |
| from typing import Dict, List, Optional, Union, Any | |
| import logging | |
| from urllib.parse import urljoin | |
| # Import from current package if possible, otherwise create placeholders | |
| try: | |
| from .exceptions import CasibaseConnectionError | |
| from .config import get_config | |
| from .logger import get_logger | |
| logger = get_logger(__name__) | |
| except ImportError: | |
| # Create minimal implementations if the imports fail | |
| class CasibaseConnectionError(Exception): | |
| \"\"\"Exception raised for Casibase connection errors.\"\"\" | |
| pass | |
| def get_config(): | |
| \"\"\"Get configuration values.\"\"\" | |
| return {} | |
| # Set up basic logger | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class CasibaseConnector: | |
| \"\"\" | |
| Connector for integrating with Casibase services. | |
| \"\"\" | |
| def __init__( | |
| self, | |
| base_url: Optional[str] = None, | |
| api_key: Optional[str] = None, | |
| store_name: Optional[str] = None, | |
| timeout: int = 30 | |
| ): | |
| \"\"\" | |
| Initialize the Casibase connector. | |
| Args: | |
| base_url: Base URL for the Casibase API | |
| api_key: API key for authentication | |
| store_name: Default store name to use | |
| timeout: Request timeout in seconds | |
| \"\"\" | |
| config = get_config() | |
| self.base_url = base_url or config.get("CASIBASE_URL", "http://localhost:7001") | |
| self.base_url = self.base_url.rstrip('/') # Remove trailing slash if present | |
| self.api_key = api_key or config.get("CASIBASE_API_KEY", "") | |
| self.store_name = store_name or config.get("CASIBASE_STORE", "default") | |
| self.timeout = timeout | |
| self.headers = { | |
| "Content-Type": "application/json", | |
| "Accept": "application/json" | |
| } | |
| if self.api_key: | |
| self.headers["Authorization"] = f"Bearer {self.api_key}" | |
| logger.info(f"Initialized Casibase connector with base_url: {self.base_url}") | |
| def _make_request( | |
| self, | |
| method: str, | |
| endpoint: str, | |
| data: Optional[Dict[str, Any]] = None, | |
| files: Optional[Dict[str, Any]] = None, | |
| params: Optional[Dict[str, Any]] = None | |
| ) -> Dict[str, Any]: | |
| \"\"\" | |
| Make a request to the Casibase API. | |
| Args: | |
| method: HTTP method (GET, POST, etc.) | |
| endpoint: API endpoint | |
| data: Request data | |
| files: Files to upload | |
| params: Query parameters | |
| Returns: | |
| Response data as dictionary | |
| Raises: | |
| CasibaseConnectionError: If the request fails | |
| \"\"\" | |
| url = urljoin(self.base_url, endpoint) | |
| # If sending files, don't include Content-Type header | |
| request_headers = self.headers.copy() | |
| if files: | |
| request_headers.pop("Content-Type", None) | |
| try: | |
| if method.upper() == "GET": | |
| response = requests.get( | |
| url, | |
| params=params, | |
| headers=request_headers, | |
| timeout=self.timeout | |
| ) | |
| elif method.upper() == "POST": | |
| if files: | |
| # Multipart form data for file uploads | |
| response = requests.post( | |
| url, | |
| data=data, # Form data | |
| files=files, | |
| headers=request_headers, | |
| timeout=self.timeout | |
| ) | |
| else: | |
| # JSON data | |
| response = requests.post( | |
| url, | |
| json=data, | |
| params=params, | |
| headers=request_headers, | |
| timeout=self.timeout | |
| ) | |
| else: | |
| raise CasibaseConnectionError(f"Unsupported HTTP method: {method}") | |
| # Check for errors | |
| if response.status_code >= 400: | |
| error_msg = f"Casibase API error: {response.status_code} - {response.text}" | |
| logger.error(error_msg) | |
| raise CasibaseConnectionError(error_msg) | |
| # Return JSON response | |
| if response.content: | |
| return response.json() | |
| else: | |
| return {"status": "success"} | |
| except requests.RequestException as e: | |
| error_msg = f"Casibase API request failed: {str(e)}" | |
| logger.error(error_msg) | |
| raise CasibaseConnectionError(error_msg) from e | |
| def query( | |
| self, | |
| query_text: str, | |
| store_name: Optional[str] = None, | |
| model_name: Optional[str] = None, | |
| top_k: int = 5, | |
| system_prompt: Optional[str] = None | |
| ) -> Dict[str, Any]: | |
| \"\"\" | |
| Query Casibase with the given text. | |
| Args: | |
| query_text: Query text | |
| store_name: Store name to query (defaults to instance store_name) | |
| model_name: Model name to use for the query | |
| top_k: Number of documents to retrieve | |
| system_prompt: Optional system prompt to include | |
| Returns: | |
| Query response | |
| \"\"\" | |
| store = store_name or self.store_name | |
| payload = { | |
| "question": query_text, | |
| "store": store, | |
| "topK": top_k | |
| } | |
| if model_name: | |
| payload["modelName"] = model_name | |
| if system_prompt: | |
| payload["systemPrompt"] = system_prompt | |
| logger.debug(f"Querying Casibase: {payload}") | |
| return self._make_request("POST", "/api/query", data=payload) | |
| def upload_file( | |
| self, | |
| file_path: str, | |
| store_name: Optional[str] = None, | |
| metadata: Optional[Dict[str, Any]] = None | |
| ) -> Dict[str, Any]: | |
| \"\"\" | |
| Upload a file to Casibase. | |
| Args: | |
| file_path: Path to the file | |
| store_name: Store name to upload to (defaults to instance store_name) | |
| metadata: Additional metadata to associate with the file | |
| Returns: | |
| Upload response | |
| \"\"\" | |
| if not os.path.exists(file_path): | |
| raise FileNotFoundError(f"File not found: {file_path}") | |
| store = store_name or self.store_name | |
| metadata = metadata or {} | |
| with open(file_path, "rb") as f: | |
| files = {"file": (os.path.basename(file_path), f)} | |
| data = {"store": store} | |
| if metadata: | |
| data["metadata"] = json.dumps(metadata) | |
| logger.debug(f"Uploading file to Casibase: {file_path} to store {store}") | |
| return self._make_request("POST", "/api/upload-file", data=data, files=files) | |
| def get_stores(self) -> List[Dict[str, Any]]: | |
| \"\"\" | |
| Get available Casibase stores. | |
| Returns: | |
| List of stores | |
| \"\"\" | |
| response = self._make_request("GET", "/api/get-stores") | |
| return response.get("data", []) | |
| def search_similar( | |
| self, | |
| query_text: str, | |
| store_name: Optional[str] = None, | |
| limit: int = 5 | |
| ) -> List[Dict[str, Any]]: | |
| \"\"\" | |
| Search for similar documents in Casibase. | |
| Args: | |
| query_text: Text to search for | |
| store_name: Store to search in (defaults to instance store_name) | |
| limit: Maximum number of results | |
| Returns: | |
| List of similar documents | |
| \"\"\" | |
| store = store_name or self.store_name | |
| payload = { | |
| "query": query_text, | |
| "store": store, | |
| "limit": limit | |
| } | |
| logger.debug(f"Searching similar documents in Casibase: {payload}") | |
| response = self._make_request("POST", "/api/search", data=payload) | |
| return response.get("data", []) | |
| def get_health(self) -> Dict[str, Any]: | |
| \"\"\" | |
| Check if the Casibase service is healthy. | |
| Returns: | |
| Health status | |
| \"\"\" | |
| try: | |
| return self._make_request("GET", "/api/health-check") | |
| except CasibaseConnectionError: | |
| return {"status": "error", "message": "Casibase service is unreachable"} | |
| """ | |
| with open(path, "w") as f: | |
| f.write(connector_code.lstrip()) | |
| def update_api_server(self) -> None: | |
| """Update the API server to include Casibase integration.""" | |
| api_server_path = self.openmanus_path / "app" / "api_server.py" | |
| if not api_server_path.exists(): | |
| logger.error(f"API server file not found at {api_server_path}") | |
| return | |
| logger.info("Checking API server for Casibase integration...") | |
| with open(api_server_path, "r") as f: | |
| content = f.read() | |
| # Check if Casibase is already integrated | |
| if "CasibaseConnector" in content and "casibase_connector" in content: | |
| logger.info("Casibase already integrated in API server") | |
| return | |
| # Add imports | |
| if "import argparse" in content: | |
| content = content.replace( | |
| "import argparse", | |
| "import argparse\nfrom .casibase_connector import CasibaseConnector" | |
| ) | |
| # Add endpoint for Casibase bridge | |
| if "def create_app():" in content: | |
| casibase_routes = """ | |
| # Casibase integration routes | |
| @app.route('/api/casibase/health', methods=['GET']) | |
| def casibase_health(): | |
| try: | |
| casibase = CasibaseConnector() | |
| health_status = casibase.get_health() | |
| return jsonify(health_status) | |
| except Exception as e: | |
| return jsonify({"status": "error", "message": str(e)}), 500 | |
| @app.route('/api/casibase/query', methods=['POST']) | |
| def casibase_query(): | |
| try: | |
| data = request.json | |
| query_text = data.get('query_text') | |
| store_name = data.get('store_name') | |
| model_name = data.get('model_name') | |
| if not query_text: | |
| return jsonify({"status": "error", "message": "query_text is required"}), 400 | |
| casibase = CasibaseConnector() | |
| result = casibase.query( | |
| query_text=query_text, | |
| store_name=store_name, | |
| model_name=model_name | |
| ) | |
| return jsonify(result) | |
| except Exception as e: | |
| return jsonify({"status": "error", "message": str(e)}), 500 | |
| @app.route('/api/casibase/search', methods=['POST']) | |
| def casibase_search(): | |
| try: | |
| data = request.json | |
| query_text = data.get('query_text') | |
| store_name = data.get('store_name') | |
| limit = data.get('limit', 5) | |
| if not query_text: | |
| return jsonify({"status": "error", "message": "query_text is required"}), 400 | |
| casibase = CasibaseConnector() | |
| result = casibase.search_similar( | |
| query_text=query_text, | |
| store_name=store_name, | |
| limit=limit | |
| ) | |
| return jsonify({"status": "success", "data": result}) | |
| except Exception as e: | |
| return jsonify({"status": "error", "message": str(e)}), 500 | |
| """ | |
| # Insert the Casibase routes before the return statement | |
| if "return app" in content: | |
| content = content.replace( | |
| "return app", | |
| f"{casibase_routes}\n return app" | |
| ) | |
| # Add imports for Flask if needed | |
| if "from flask import " in content: | |
| if "jsonify" not in content: | |
| content = content.replace( | |
| "from flask import ", | |
| "from flask import jsonify, " | |
| ) | |
| if "request" not in content: | |
| content = content.replace( | |
| "from flask import ", | |
| "from flask import request, " | |
| ) | |
| # Write back the updated content | |
| with open(api_server_path, "w") as f: | |
| f.write(content) | |
| logger.info("API server updated with Casibase integration") | |
| def run_fixes(self) -> None: | |
| """Run all fixes.""" | |
| logger.info("Starting OpenManus fixes...") | |
| self.fix_requirements() | |
| self.install_dependencies() | |
| self.ensure_casibase_connector() | |
| self.update_api_server() | |
| logger.info("OpenManus fixes completed successfully") | |
| def main(): | |
| """Main entry point.""" | |
| try: | |
| fixer = OpenManusFixer() | |
| fixer.run_fixes() | |
| return 0 | |
| except Exception as e: | |
| logger.error(f"Error: {e}") | |
| return 1 | |
| if __name__ == "__main__": | |
| sys.exit(main()) |