AgentEvalTool/backend/cli/run.py
sinohqb 739d586aec feat(backend): v0.4 triggered_by tracking, login gate, compare guard, dashboard stats
- 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
2026-07-28 17:40:54 +08:00

133 lines
4.3 KiB
Python

"""CLI commands for evaluation execution."""
import asyncio
from typing import Any
import typer
from agenteval.evaluation.engine import EvalEngine
from agenteval.models import RunTrigger
from agenteval.storage.repository import RunRepository, ScenarioRepository, TargetRepository
from rich.console import Console
from rich.table import Table
app = typer.Typer(help="评测执行")
console = Console()
def _progress(event: str, data: dict[str, Any]) -> None:
if event == "case_start":
console.print(f"[用例 {data['index']}/{data['total']}] {data['case_id']}")
elif event == "turn_end":
console.print(f"{data['round']} 轮回复,耗时 {data['latency_ms']}ms")
elif event == "rule_result":
style = "green" if data["passed"] else "red"
console.print(
f" [{data['rule_type']}] {'通过' if data['passed'] else '失败'} - {data['reason']}", style=style
)
elif event == "error":
console.print(f"执行异常: {data['error']}", style="red")
@app.command("start", help="启动一次评测")
def start_run(
target_id: str = typer.Option(..., help="评测对象 ID"),
scenario_id: str = typer.Option(..., help="评测场景 ID"),
output_json: bool = typer.Option(False, "--output-json", help="运行结束后输出 JSON 报告"),
) -> None:
target = TargetRepository().get(target_id)
if not target:
console.print(f"评测对象不存在: {target_id}", style="red")
raise typer.Exit(1)
scenario = ScenarioRepository().get(scenario_id)
if not scenario:
console.print(f"评测场景不存在: {scenario_id}", style="red")
raise typer.Exit(1)
console.print(f"开始评测: 对象={target.name}, 场景={scenario.name}")
engine = EvalEngine(target=target, scenario=scenario, triggered_by=RunTrigger.CLI)
try:
run = asyncio.run(engine.run(progress_callback=_progress))
except Exception as exc:
console.print(f"评测失败: {exc}", style="red")
raise typer.Exit(1)
console.print(f"评测完成: run_id={run.id}, status={run.status.value}")
if run.summary:
console.print_json(data=run.summary)
if output_json:
from agenteval.evaluation.report import render_json_report
console.print(render_json_report(run.id))
@app.command("list", help="列出评测执行记录")
def list_runs() -> None:
runs = RunRepository().list_all()
if not runs:
console.print("暂无评测记录")
return
table = Table(show_header=True, header_style="bold")
table.add_column("Run ID")
table.add_column("对象 ID")
table.add_column("场景 ID")
table.add_column("状态")
table.add_column("开始时间")
table.add_column("完成时间")
for run in runs:
table.add_row(
run.id or "",
run.target_id,
run.scenario_id,
run.status.value,
str(run.started_at)[:19] if run.started_at else "",
str(run.completed_at)[:19] if run.completed_at else "",
)
console.print(table)
@app.command("status", help="查看评测执行状态")
def run_status(run_id: str) -> None:
run = RunRepository().get(run_id)
if not run:
console.print(f"评测记录不存在: {run_id}", style="red")
raise typer.Exit(1)
console.print_json(data=run.model_dump())
@app.command("logs", help="查看评测执行日志(对话轮次)")
def run_logs(run_id: str) -> None:
repo = RunRepository()
run = repo.get(run_id)
if not run:
console.print(f"评测记录不存在: {run_id}", style="red")
raise typer.Exit(1)
turns = repo.get_turns(run_id)
if not turns:
console.print("暂无对话记录")
return
table = Table(show_header=True, header_style="bold")
table.add_column("Case ID")
table.add_column("轮次")
table.add_column("消息 ID")
table.add_column("耗时(ms)")
table.add_column("发送时间")
table.add_column("回复时间")
for turn in turns:
table.add_row(
turn.case_id,
str(turn.round_index),
turn.question_msg_id or "",
str(turn.latency_ms) if turn.latency_ms is not None else "",
str(turn.sent_at)[:19] if turn.sent_at else "",
str(turn.received_at)[:19] if turn.received_at else "",
)
console.print(table)