架构重构(候选 1-6): - storage/repository.py 按域拆分为包(target/scenario/run/campaign/result) - storage/db.py 按域拆分为包(eval/campaign/file/model_config/intelligent_eval) - intelligent_eval/lifecycle.py 按状态机阶段拆分为包 - services/runs.py 编排逻辑下沉 - Campaigns.tsx 拆分为 campaigns/ 子组件 测试补全(候选 7): 前端(+125 用例,107→232): - utils/ 纯函数:date/campaignTime/ruleLabels/fileTree/fileFormat/colors - stores/tabStore 状态管理 - 核心组件:FormDrawer/PageWrapper/ChatBubble/GeneratedMessages/SectionHeader/StatCard/TurnList - 业务组件:CaseBlock/CaseDetail/RuleOverview/WindowTimeline/RunList/TabBar/CampaignRunTimeline - 文件管理:FileCategoryTree/FileTable - hooks:sessionReducer/useFiles/useRunSession 后端(+38 用例,916→954): - targets API CRUD + 404 路径 - WebSocket 连接管理器 - proxy 头部重写(CSP/X-Frame-Options) - target 仓储 update 方法 - app 健康检查 + SPA 404 - scenarios 模板端点 + 404 - files API 边缘分支(404 场景 + 500 兜底) - files service update_category - 智能评估状态机迁移测试 门禁状态: - 前端:tsc 干净 + 232 passed - 后端:954 passed + ruff 全绿
87 lines
2.7 KiB
Python
87 lines
2.7 KiB
Python
"""API routes for evaluation runs.
|
|
|
|
Thin HTTP translation layer: lifecycle orchestration and the ``/logs``
|
|
assembly live in :mod:`agenteval.services.runs`.
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlmodel import Session
|
|
|
|
from agenteval.models import RunTrigger
|
|
from agenteval.services import runs as run_service
|
|
from agenteval.storage.repository import RunRepository, ScenarioRepository, TargetRepository
|
|
from agenteval.web.deps import get_db
|
|
from agenteval.web.websocket import ws_manager
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class StartRunRequest(BaseModel):
|
|
target_id: str
|
|
scenario_id: str
|
|
triggered_by: RunTrigger = RunTrigger.MANUAL
|
|
|
|
|
|
def _progress_callback(run_id: str):
|
|
return lambda event, data: ws_manager.emit(run_id, event, data)
|
|
|
|
|
|
@router.get("")
|
|
async def list_runs(session: Session = Depends(get_db)) -> list[dict]:
|
|
scenario_names = ScenarioRepository(session).name_map()
|
|
target_names = {t.id: t.name for t in TargetRepository(session).list_all()}
|
|
return [
|
|
{
|
|
**r.model_dump(),
|
|
"scenario_name": scenario_names.get(r.scenario_id),
|
|
"target_name": target_names.get(r.target_id),
|
|
}
|
|
for r in RunRepository(session).list_all()
|
|
]
|
|
|
|
|
|
@router.post("")
|
|
async def start_run(
|
|
request: StartRunRequest,
|
|
session: Session = Depends(get_db),
|
|
) -> dict:
|
|
try:
|
|
run = run_service.start_run(
|
|
session,
|
|
request.target_id,
|
|
request.scenario_id,
|
|
request.triggered_by,
|
|
on_progress=_progress_callback,
|
|
)
|
|
except run_service.RunStartError as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
return run.model_dump()
|
|
|
|
|
|
@router.get("/{run_id}")
|
|
async def get_run(run_id: str, session: Session = Depends(get_db)) -> dict:
|
|
run = RunRepository(session).get(run_id)
|
|
if not run:
|
|
raise HTTPException(status_code=404, detail="run not found")
|
|
return run.model_dump()
|
|
|
|
|
|
@router.post("/{run_id}/cancel")
|
|
async def cancel_run(run_id: str, session: Session = Depends(get_db)) -> dict:
|
|
try:
|
|
run = run_service.cancel_run(session, run_id)
|
|
except run_service.RunNotFoundError as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
except run_service.RunNotCancellableError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
return run.model_dump()
|
|
|
|
|
|
@router.get("/{run_id}/logs")
|
|
async def get_run_logs(run_id: str, session: Session = Depends(get_db)) -> dict:
|
|
try:
|
|
return run_service.build_run_logs(session, run_id)
|
|
except run_service.RunNotFoundError as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|