first commit
This commit is contained in:
+267
@@ -0,0 +1,267 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user