#!/usr/bin/env python3 """ CADDesigner API server startup script. """ import sys import os import argparse import signal import shutil from pathlib import Path from bootstrap_env import load_project_env load_project_env() # Add the project root directory to the Python path. project_root = Path(__file__).parent sys.path.insert(0, str(project_root)) default_working_dir = project_root / "workspace" def resolve_display_host(host: str) -> str: """Convert a bind address to a display address better suited for browser access.""" if host in {"0.0.0.0", "::"}: return "localhost" return host def validate_directory(path_str: str) -> Path: """Validate and return a valid directory path.""" path = Path(path_str).resolve() if not path.exists(): try: path.mkdir(parents=True, exist_ok=True) print(f"Created working directory: {path}") except Exception as e: raise argparse.ArgumentTypeError(f"Unable to create directory {path}: {e}") elif not path.is_dir(): raise argparse.ArgumentTypeError(f"Path {path} is not a directory") return path def prepare_working_directory(working_dir: Path) -> None: """Prepare runtime workspace assets inside the active working directory.""" source_skills = project_root / "workspace" / "skills" target_skills = working_dir / "skills" if source_skills.exists() and source_skills.resolve() != target_skills.resolve(): shutil.copytree(source_skills, target_skills, dirs_exist_ok=True) def check_config(): from SimpleLLMFunc.logger import app_log """Check the configuration file.""" config_file = project_root / "config" / "provider.json" template_file = project_root / "config" / "provider_template.json" if not config_file.exists(): print("Configuration file does not exist") if template_file.exists(): try: import shutil shutil.copy2(template_file, config_file) app_log("Created configuration file from template") print(f"Please edit the configuration file: {config_file}") print("You can modify the configuration later; continuing service startup now...") except Exception as e: print(f"Failed to copy configuration template: {e}") return False else: print("Configuration template file does not exist; please check the project structure") return False else: app_log("Configuration file check passed") return True def signal_handler(signum, frame): """Signal handler for graceful shutdown.""" print(f"\nReceived signal {signum}; shutting down the API server...") sys.exit(0) def main(): """Main function.""" parser = argparse.ArgumentParser( description="CADDesigner API server launcher", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Usage examples: # Start with the default configuration %(prog)s # Customize host and port %(prog)s --host 0.0.0.0 --port 8001 # Enable development mode (auto reload) %(prog)s --reload # Adjust the number of worker processes %(prog)s --workers 4 # Start in a specific working directory %(prog)s --working-dir /path/to/workspace # Production environment configuration example %(prog)s --host 0.0.0.0 --port 8000 \\ --workers 4 --log-level info \\ --working-dir /var/lib/caddesigner API endpoints: GET / # Server information GET /health # Health check GET /v1/models # List available models POST /v1/chat/completions # Chat completion (OpenAI compatible) GET /v1/conversations # Conversation management GET /docs # Swagger API documentation GET /redoc # ReDoc API documentation """, ) # Basic server parameters. parser.add_argument( "--host", default="0.0.0.0", help="API server host address (default: 0.0.0.0)" ) parser.add_argument( "--port", type=int, default=8000, help="API server port (default: 8000)" ) parser.add_argument( "--working-dir", type=validate_directory, default=default_working_dir, help="Working directory path (default: ./workspace)", ) # Development and debugging parameters. parser.add_argument( "--reload", action="store_true", help="Enable development mode (auto reload after file changes)" ) parser.add_argument( "--log-level", choices=["debug", "info", "warning", "error", "critical"], default="info", help="Log level (default: info)", ) parser.add_argument( "--access-log", action="store_true", default=True, help="Enable access log (default: enabled)", ) # Performance parameters. parser.add_argument( "--workers", type=int, default=1, help="Number of worker processes (default: 1; forced to 1 in reload mode)", ) parser.add_argument( "--loop", choices=["auto", "asyncio", "uvloop"], default="auto", help="Event loop type (default: auto)", ) # Security and limit parameters. parser.add_argument("--limit-concurrency", type=int, help="Maximum concurrent connection limit") parser.add_argument("--limit-max-requests", type=int, help="Maximum requests handled per process") # Debugging parameters. parser.add_argument("--debug", action="store_true", help="Enable debug mode") args = parser.parse_args() # Set signal handlers. signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # Prepare and switch to the working directory. original_dir = os.getcwd() prepare_working_directory(args.working_dir) os.chdir(args.working_dir) try: import uvicorn from web_interface.server import app except ImportError as e: print(f"Import error: {e}") print( "Please ensure all dependencies are installed: uv sync (development environment) or pip install -r requirements.txt" ) sys.exit(1) try: display_host = resolve_display_host(args.host) print("Starting CADDesigner API server...") print("=" * 60) print(f"Server address: http://{display_host}:{args.port}") print(f"API documentation: http://{display_host}:{args.port}/docs") print(f"ReDoc documentation: http://{display_host}:{args.port}/redoc") print(f"Health check: http://{display_host}:{args.port}/health") print(f"Working directory: {args.working_dir}") if args.reload: print("Development mode: enabled (auto reload)") if args.workers > 1 and not args.reload: print(f"Worker processes: {args.workers}") if args.debug: print("Debug mode: enabled") print("Press Ctrl+C to stop the service") print("=" * 60) # Check configuration. if not check_config(): print("Configuration check failed; please check the configuration file") sys.exit(1) # Prepare uvicorn configuration. uvicorn_config = { "app": "web_interface.server:app", "host": args.host, "port": args.port, "reload": args.reload, "log_level": args.log_level, "access_log": args.access_log, "workers": 1 if args.reload else args.workers, # Only one worker can be used in reload mode. "loop": args.loop, } # Add optional parameters. if args.limit_concurrency: uvicorn_config["limit_concurrency"] = args.limit_concurrency if args.limit_max_requests: uvicorn_config["limit_max_requests"] = args.limit_max_requests # Start the server. uvicorn.run(**uvicorn_config) except KeyboardInterrupt: print("\nAPI server stopped") except Exception as e: print(f"Server startup failed: {e}") if args.debug: import traceback traceback.print_exc() sys.exit(1) finally: # Restore the original working directory. os.chdir(original_dir) if __name__ == "__main__": main()