- EvalRun.triggered_by 全链路(manual/ai_assistant/cli)+ 迁移 b7d4e6f81c22 - 标准 agenteval-run SKILL.md 纳入版本管理,deploy 脚本同步 + API Key 注入 - 简单登录:AGENTEVAL_ADMIN_PASSWORD + HMAC 会话 token,require_auth 双凭据 - 对比报告限同场景(400)+ 空 results 误判修复 - /api/stats/dashboard 扩展聚合;/api/runs 返回场景/对象名 - 测试 218 → 232
107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
"""FastAPI web backend for AgentEvalTool."""
|
|
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from fastapi import Depends, FastAPI, WebSocket, WebSocketDisconnect
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import FileResponse, JSONResponse
|
|
|
|
from agenteval.config import get_settings
|
|
from agenteval.storage.db import init_db
|
|
from agenteval.version import get_build_info, get_version
|
|
from agenteval.web.deps import require_api_key
|
|
from agenteval.web.routers import auth, files, model_configs, proxy, reports, runs, scenarios, stats, targets
|
|
from agenteval.web.websocket import ws_manager
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_: FastAPI):
|
|
init_db()
|
|
yield
|
|
|
|
|
|
app = FastAPI(
|
|
title="AgentEvalTool",
|
|
description="智能体质量评估工具集平台 Web API",
|
|
version=get_version(),
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
settings = get_settings()
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.allowed_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
_api_deps = [Depends(require_api_key)]
|
|
|
|
# Login endpoints must stay open — they are how the client obtains credentials.
|
|
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
|
|
app.include_router(targets.router, prefix="/api/targets", tags=["targets"], dependencies=_api_deps)
|
|
app.include_router(scenarios.router, prefix="/api/scenarios", tags=["scenarios"], dependencies=_api_deps)
|
|
app.include_router(runs.router, prefix="/api/runs", tags=["runs"], dependencies=_api_deps)
|
|
app.include_router(reports.router, prefix="/api/reports", tags=["reports"], dependencies=_api_deps)
|
|
app.include_router(stats.router, prefix="/api/stats", tags=["stats"], dependencies=_api_deps)
|
|
app.include_router(files.router, prefix="/api/files", tags=["files"], dependencies=_api_deps)
|
|
app.include_router(
|
|
model_configs.router,
|
|
prefix="/api/model-configs",
|
|
tags=["model-configs"],
|
|
dependencies=_api_deps,
|
|
)
|
|
|
|
|
|
@app.websocket("/openclaw")
|
|
async def openclaw_ws_root(websocket: WebSocket):
|
|
url = proxy.get_ws_upstream()
|
|
if websocket.query_params:
|
|
url += f"?{websocket.query_params}"
|
|
await proxy.ws_bridge(websocket, url)
|
|
|
|
|
|
@app.websocket("/openclaw/{path:path}")
|
|
async def openclaw_ws_proxy(websocket: WebSocket, path: str):
|
|
url = f"{proxy.get_ws_upstream()}/{path}"
|
|
if websocket.query_params:
|
|
url += f"?{websocket.query_params}"
|
|
await proxy.ws_bridge(websocket, url)
|
|
|
|
|
|
app.include_router(proxy.router, prefix="/openclaw", tags=["proxy"])
|
|
|
|
_default_dist = Path(__file__).resolve().parent.parent.parent.parent / "frontend" / "web" / "dist"
|
|
WEB_DIST = Path(settings.frontend_dist_path) if settings.frontend_dist_path else _default_dist
|
|
if WEB_DIST.exists():
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
app.mount("/assets", StaticFiles(directory=WEB_DIST / "assets"), name="assets")
|
|
|
|
|
|
@app.get("/api/health", tags=["health"])
|
|
def health() -> dict:
|
|
return {"status": "ok", **get_build_info()}
|
|
|
|
|
|
@app.websocket("/ws/runs/{run_id}")
|
|
async def websocket_run_progress(websocket: WebSocket, run_id: str):
|
|
await ws_manager.connect(run_id, websocket)
|
|
try:
|
|
while True:
|
|
await websocket.receive_text()
|
|
except WebSocketDisconnect:
|
|
ws_manager.disconnect(run_id, websocket)
|
|
|
|
|
|
@app.get("/{full_path:path}")
|
|
def serve_spa(full_path: str):
|
|
"""Serve the React SPA for all non-API routes."""
|
|
index_file = WEB_DIST / "index.html"
|
|
if index_file.exists():
|
|
return FileResponse(index_file)
|
|
return JSONResponse({"detail": "frontend not built"}, status_code=404)
|