AgentEvalTool/backend/agenteval/intelligent_eval/lifecycle/plan_management.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

138 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Plan management phase: create, plan, approve/reject, cancel, delete, reads."""
from typing import Any, Optional
from sqlmodel import Session
from agenteval.intelligent_eval.lifecycle._core import (
IntelligentEvalNotFoundError,
get_or_raise,
resolve_write,
transition,
)
from agenteval.intelligent_eval.models import IntelligentEval, IntelligentEvalStatus
from agenteval.intelligent_eval.repository import IntelligentEvalRepository
from agenteval.storage.db import IntelligentEvalDB, utc_now
from agenteval.storage.repository import TargetRepository
def create_eval(
session: Session,
*,
name: str,
target_id: str,
goal: str,
seeds: dict[str, Any],
intent: str,
role_description: str,
time_window_hours: int = 24,
) -> IntelligentEval:
"""创建智能评估并直接进入 planning 状态draft → planning 一步完成)。"""
from agenteval.intelligent_eval.config_snapshot import save_snapshot
if TargetRepository(session).get(target_id) is None:
raise IntelligentEvalNotFoundError(f"target {target_id} not found")
repo = IntelligentEvalRepository(session)
ev = repo._create(
IntelligentEval(
name=name,
target_id=target_id,
status=IntelligentEvalStatus.PLANNING,
goal=goal,
seeds=seeds,
intent=intent,
role_description=role_description,
time_window_hours=time_window_hours,
created_at=utc_now(),
updated_at=utc_now(),
)
)
# Save config snapshot
eval_db = session.get(IntelligentEvalDB, ev.id)
if eval_db:
save_snapshot(eval_db, "created", "user", session)
return ev
def submit_plan(session: Session, eval_id: str, plan: dict[str, Any]) -> IntelligentEval:
"""OpenClaw 提交粗计划planning → pending_approval。"""
from agenteval.intelligent_eval.config_snapshot import save_snapshot
repo = IntelligentEvalRepository(session)
result = repo._submit_plan_if_planning(eval_id, plan)
ev = resolve_write(
eval_id,
result,
expected=IntelligentEvalStatus.PLANNING,
target=IntelligentEvalStatus.PENDING_APPROVAL,
)
# Save config snapshot
eval_db = session.get(IntelligentEvalDB, eval_id)
if eval_db:
save_snapshot(eval_db, "plan_submitted", "openclaw", session)
return ev
def approve(session: Session, eval_id: str) -> IntelligentEval:
"""用户批准pending_approval → executing。"""
repo = IntelligentEvalRepository(session)
ev = get_or_raise(repo, eval_id)
return transition(repo, ev, IntelligentEvalStatus.EXECUTING)
def reject(session: Session, eval_id: str, feedback: str) -> IntelligentEval:
"""用户打回pending_approval → planning附反馈"""
repo = IntelligentEvalRepository(session)
result = repo._reject_plan_if_pending(eval_id, feedback)
return resolve_write(
eval_id,
result,
expected=IntelligentEvalStatus.PENDING_APPROVAL,
target=IntelligentEvalStatus.PLANNING,
)
def cancel(session: Session, eval_id: str) -> IntelligentEval:
"""用户取消pending_approval / executing → cancelled。"""
repo = IntelligentEvalRepository(session)
ev = get_or_raise(repo, eval_id)
return transition(repo, ev, IntelligentEvalStatus.CANCELLED)
def delete_eval(session: Session, eval_id: str) -> IntelligentEval:
"""逻辑删除completed / cancelled / failed → deleted。幂等已删除直接返回。"""
repo = IntelligentEvalRepository(session)
ev = repo.get_including_deleted(eval_id)
if ev is None:
raise IntelligentEvalNotFoundError(f"intelligent eval {eval_id} not found")
if ev.status is IntelligentEvalStatus.DELETED:
return ev
return transition(repo, ev, IntelligentEvalStatus.DELETED)
def get_eval(session: Session, eval_id: str) -> IntelligentEval:
repo = IntelligentEvalRepository(session)
return get_or_raise(repo, eval_id)
def list_evals(session: Session) -> list[IntelligentEval]:
return IntelligentEvalRepository(session).list_all()
def list_evals_page(
session: Session, offset: int, limit: int, status: Optional[str] = None
) -> tuple[list[IntelligentEval], int, dict[str, int]]:
"""Return one page of evals (newest first), total count, and per-status counts."""
repo = IntelligentEvalRepository(session)
return repo.list_page(offset, limit, status), repo.count(), repo.count_by_status()
def eval_status_counts(session: Session) -> dict[str, int]:
"""Count evaluations per status (for the list page stat bar)."""
return IntelligentEvalRepository(session).count_by_status()