From dcf53524b2c6c410a85e1e7ab2ac4ec75a1732d3 Mon Sep 17 00:00:00 2001 From: zhaoxi Date: Fri, 24 Apr 2026 12:48:17 +0800 Subject: [PATCH] =?UTF-8?q?wip:=20=E4=BF=AE=E5=A4=8D=E4=BA=86serve?= =?UTF-8?q?=E7=9A=84=E9=83=A8=E5=88=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 5 - main.py | 12 ++- pretor/api/cluster.py | 10 +- pretor/core/api/__init__.py | 176 +++++++++++++++++++----------------- 4 files changed, 108 insertions(+), 95 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9694916..ab50eff 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,8 +10,6 @@ services: POSTGRES_DB: pretor ports: - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d pretor"] interval: 5s @@ -34,9 +32,6 @@ services: - POSTGRES_PORT=5432 - POSTGRES_DB=pretor - SECRET_KEY=changethiskey12345 - volumes: - - .:/app - - /app/frontend/dist # Prevent local uncompiled frontend from overriding the built one volumes: postgres_data: diff --git a/main.py b/main.py index ad6d8b3..ae28a17 100644 --- a/main.py +++ b/main.py @@ -10,20 +10,26 @@ from pretor.core.individual.control_node.control_node import ControlNode from pretor.core.workflow.workflow_runner import WorkflowRunningEngine from pretor.core.api import PretorGateway from ray import serve +import os + async def start_system(): # 1. 初始化 Ray - + db_host = os.getenv("POSTGRES_HOST", "db") env_vars = { "POSTGRES_USER": "postgres", "POSTGRES_PASSWORD": "postgres", - "POSTGRES_HOST": "127.0.0.1", + "POSTGRES_HOST": db_host, "POSTGRES_PORT": "5432", "POSTGRES_DB": "postgres", "SECRET_KEY": "yoursecretkey" } - ray.init(ignore_reinit_error=True, runtime_env={"env_vars": env_vars}) + + ray.init(ignore_reinit_error=True, + dashboard_host="0.0.0.0", + dashboard_port=8265, + runtime_env={"env_vars": env_vars}) # 2. 启动数据库组件 diff --git a/pretor/api/cluster.py b/pretor/api/cluster.py index 54076ac..bc986be 100644 --- a/pretor/api/cluster.py +++ b/pretor/api/cluster.py @@ -26,11 +26,11 @@ async def update_cluster_state(websocket: WebSocket): nodes = ray.nodes() payload = [ { - "node_id": node["NodeID"], - "node_name": node["NodeName"], - "alive": node["Alive"], - "resources": node["Resources"], - "remaining": node["RemainingResources"] + "node_id": node.get("NodeID"), + "node_name": node.get("NodeName"), + "alive": node.get("Alive"), + "resources": node.get("Resources", {}), + "remaining": node.get("RemainingResources", {}) } for node in nodes ] diff --git a/pretor/core/api/__init__.py b/pretor/core/api/__init__.py index 25a0a35..7f27692 100644 --- a/pretor/core/api/__init__.py +++ b/pretor/core/api/__init__.py @@ -12,104 +12,116 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict -from fastapi import FastAPI,WebSocket -from fastapi.staticfiles import StaticFiles -from fastapi.responses import FileResponse import os +from typing import Dict +from fastapi import FastAPI, WebSocket, Request +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse, JSONResponse + from pretor.api.platform.frontend import client_router from pretor.api.auth import auth_router from pretor.api.provider import provider_router from pretor.api.resource import resource_router from pretor.api.cluster import cluster_router from pretor.api.agent import agent_router -from fastapi.responses import JSONResponse -from fastapi import Request from pretor.utils.error import ( - DemandError, - ModelNotExistError, - UserError, - UserNotExistError, - UserPasswordError, - ProviderError, - ProviderNotExistError, - WorkflowError, - WorkflowExit + DemandError, ModelNotExistError, UserError, + UserNotExistError, UserPasswordError, ProviderError, + ProviderNotExistError, WorkflowError, WorkflowExit ) from ray import serve app = FastAPI() +app.include_router(client_router) # 客户端路径 +app.include_router(auth_router) # 用户路径 +app.include_router(provider_router) # 供应商路径 +app.include_router(resource_router) # 资源路径 +app.include_router(cluster_router) # 集群信息路径 +app.include_router(agent_router) # agent路径 + +@app.exception_handler(UserNotExistError) +async def user_not_exist_handler(request: Request, exc: UserNotExistError): + return JSONResponse(status_code=404, content={"message": "用户不存在"}) + + +@app.exception_handler(UserPasswordError) +async def user_password_handler(request: Request, exc: UserPasswordError): + return JSONResponse(status_code=401, content={"message": "密码错误"}) + + +@app.exception_handler(UserError) +async def user_error_handler(request: Request, exc: UserError): + return JSONResponse(status_code=400, content={"message": "用户相关错误"}) + + +@app.exception_handler(ProviderNotExistError) +async def provider_not_exist_handler(request: Request, exc: ProviderNotExistError): + return JSONResponse(status_code=404, content={"message": "服务提供商不存在"}) + + +@app.exception_handler(ProviderError) +async def provider_error_handler(request: Request, exc: ProviderError): + return JSONResponse(status_code=400, content={"message": "服务提供商错误"}) + + +@app.exception_handler(ModelNotExistError) +async def model_not_exist_handler(request: Request, exc: ModelNotExistError): + return JSONResponse(status_code=404, content={"message": "模型不存在"}) + + +@app.exception_handler(DemandError) +async def demand_error_handler(request: Request, exc: DemandError): + return JSONResponse(status_code=400, content={"message": "需求格式错误或不满足"}) + + +@app.exception_handler(WorkflowExit) +async def workflow_exit_handler(request: Request, exc: WorkflowExit): + return JSONResponse(status_code=400, content={"message": "工作流已退出"}) + + +@app.exception_handler(WorkflowError) +async def workflow_error_handler(request: Request, exc: WorkflowError): + return JSONResponse(status_code=500, content={"message": "工作流执行错误"}) + +base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) +frontend_dir = os.path.join(base_dir, "frontend", "dist") + +if os.path.exists(frontend_dir): + app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dir, "assets")), name="assets") + + + @app.get("/favicon.svg", include_in_schema=False) + async def serve_favicon(): + return FileResponse(os.path.join(frontend_dir, "favicon.svg")) + + + @app.get("/icons.svg", include_in_schema=False) + async def serve_icons(): + return FileResponse(os.path.join(frontend_dir, "icons.svg")) + + + @app.get("/{full_path:path}", include_in_schema=False) + async def serve_frontend(full_path: str): + # 【重要安全修复】避免拦截不存在的 API 路由。如果是调用了不存在的 /api/ 接口,直接返回 404,不返回前端页面 + if full_path.startswith("api/"): + return JSONResponse(status_code=404, content={"detail": "API endpoint not found"}) + + index_path = os.path.join(frontend_dir, "index.html") + if os.path.exists(index_path): + return FileResponse(index_path) + return JSONResponse(status_code=404, content={"detail": "Frontend build not found"}) +else: + import logging + + logging.getLogger("pretor").warning(f"Frontend dist folder not found at {frontend_dir}. Skipping frontend mount.") + + @serve.deployment @serve.ingress(app) class PretorGateway: gateway: Dict[str, WebSocket] + def __init__(self): - self.gateway = {} - - app.include_router(client_router)#客户端路径 - - app.include_router(auth_router)#用户路径 - app.include_router(provider_router)#供应商路径 - app.include_router(resource_router)#资源路径 - app.include_router(cluster_router)#集群信息路径 - app.include_router(agent_router)#agent路径 - - @app.exception_handler(UserNotExistError) - async def user_not_exist_handler(request: Request, exc: UserNotExistError): - return JSONResponse(status_code=404, content={"message": "用户不存在"}) - - @app.exception_handler(UserPasswordError) - async def user_password_handler(request: Request, exc: UserPasswordError): - return JSONResponse(status_code=401, content={"message": "密码错误"}) - - @app.exception_handler(UserError) - async def user_error_handler(request: Request, exc: UserError): - return JSONResponse(status_code=400, content={"message": "用户相关错误"}) - - @app.exception_handler(ProviderNotExistError) - async def provider_not_exist_handler(request: Request, exc: ProviderNotExistError): - return JSONResponse(status_code=404, content={"message": "服务提供商不存在"}) - - @app.exception_handler(ProviderError) - async def provider_error_handler(request: Request, exc: ProviderError): - return JSONResponse(status_code=400, content={"message": "服务提供商错误"}) - - @app.exception_handler(ModelNotExistError) - async def model_not_exist_handler(request: Request, exc: ModelNotExistError): - return JSONResponse(status_code=404, content={"message": "模型不存在"}) - - @app.exception_handler(DemandError) - async def demand_error_handler(request: Request, exc: DemandError): - return JSONResponse(status_code=400, content={"message": "需求格式错误或不满足"}) - - @app.exception_handler(WorkflowExit) - async def workflow_exit_handler(request: Request, exc: WorkflowExit): - return JSONResponse(status_code=400, content={"message": "工作流已退出"}) - - @app.exception_handler(WorkflowError) - async def workflow_error_handler(request: Request, exc: WorkflowError): - return JSONResponse(status_code=500, content={"message": "工作流执行错误"}) - - frontend_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), "frontend", "dist") - - if os.path.exists(frontend_dir): - app.mount("/assets", StaticFiles(directory=os.path.join(frontend_dir, "assets")), name="assets") - - # Serve favicon and other top-level static files if they exist - @app.get("/favicon.svg", include_in_schema=False) - async def serve_favicon(): - return FileResponse(os.path.join(frontend_dir, "favicon.svg")) - - @app.get("/icons.svg", include_in_schema=False) - async def serve_icons(): - return FileResponse(os.path.join(frontend_dir, "icons.svg")) - - @app.get("/{full_path:path}", include_in_schema=False) - async def serve_frontend(full_path: str): - # If a path isn't API or assets, fallback to index.html for React Router / SPA handling - # In this specific case, it also fixes any root path reloading issues - return FileResponse(os.path.join(frontend_dir, "index.html")) - - + self.gateway = {} \ No newline at end of file