AgentEvalTool/backend/agenteval/web/routers/runs.py
sinohqb 3705945a7d test: 完整测试覆盖补全(+163 用例)
架构重构(候选 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 全绿
2026-08-24 15:56:09 +08:00

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