feat: support batch repetition and selective case import

This commit is contained in:
chenmingxuan committed 2026-10-09 16:29:16 +08:00
1 parent df66897ff4
commit 4d6560dbd4
16 files changed
+296 -37

No files matched your search

+8 -1
View File
@@ -30,6 +30,7 @@ class BatchRequest(ReportPreferences):
device_serial: str = Field(min_length=1, max_length=200)
profile: Literal["flash", "pro"] = "flash"
stop_on_failure: bool = False
repeat_count: int = Field(default=1, ge=1, strict=True)
timeout_seconds: int = Field(
default_factory=lambda: settings.ARTEMIS_TASK_TIMEOUT_SECONDS,
ge=30,
@@ -52,6 +53,7 @@ def preferences():
return {
**store.preferences(),
"batch_timeout_seconds": settings.ARTEMIS_TASK_TIMEOUT_SECONDS,
"batch_max_executions": settings.ARTEMIS_BATCH_MAX_EXECUTIONS,
}
@@ -256,7 +258,12 @@ def history(
[*args, size, (page - 1) * size],
).fetchall()
items = [dict(row) for row in rows]
jobs = store.jobs_for_sessions([item["session_id"] for item in items])
for item in items:
if item["case_id"]:
if job := jobs.get(item["session_id"]):
item["name"] = task_import.execution_title(job)
item["iteration"] = job.get("iteration", 1)
item["repeat_count"] = job.get("repeat_count", 1)
elif item["case_id"]:
item["name"] = task_import.case_title(item)
return {"items": items, "total": total, "page": page, "size": size}
+5 -3
View File
@@ -28,7 +28,7 @@ try:
from admin_console.services.media_service import media_service
from admin_console.services.model_service import model_service
from admin_console.services.automation_store import store
from admin_console.services.task_import import case_title
from admin_console.services.task_import import execution_title
except ImportError:
from apps.admin_console.core.state import state
from apps.admin_console.database.repositories.session_repository import session_repo
@@ -36,7 +36,7 @@ except ImportError:
from apps.admin_console.services.media_service import media_service
from apps.admin_console.services.model_service import model_service
from apps.admin_console.services.automation_store import store
from apps.admin_console.services.task_import import case_title
from apps.admin_console.services.task_import import execution_title
router = APIRouter(tags=["sessions"])
@@ -83,7 +83,9 @@ def _list_sessions_sync():
if job:
case = job.get("case") or {}
row_dict["case_id"] = case.get("case_id") or ""
row_dict["display_name"] = case_title(case) or row_dict.get("initial_goal")
row_dict["display_name"] = execution_title(job) or row_dict.get("initial_goal")
row_dict["iteration"] = job.get("iteration", 1)
row_dict["repeat_count"] = job.get("repeat_count", 1)
row_dict["error"] = job.get("error") or ""
if job.get("status") in {"completed", "failed", "cancelled", "interrupted", "skipped"}:
row_dict["status"] = job["status"]
@@ -9,7 +9,7 @@ from uuid import UUID
from apps.admin_console.core.state import state
from apps.admin_console.services.automation_store import RECOVERY_ERRORS, store, new_id
from apps.admin_console.services.task_import import case_goal, case_title
from apps.admin_console.services.task_import import case_goal, execution_title
from artemis.config.settings import settings
from artemis.runtime.task_limits import task_timeout
@@ -297,7 +297,7 @@ async def tick_batches():
runtime_overrides=options,
session_id=job["session_id"],
ingress="batch",
display_name=case_title(job["case"]),
display_name=execution_title(job),
)
if response.get("status") == "rejected" or not response.get("tasks"):
raise ValueError(response.get("error") or "队列拒绝接收任务")
@@ -330,6 +330,15 @@ def has_legacy_replay(batch_id: str) -> bool:
def create_batch(payload: dict) -> dict:
repeat_count = payload.get("repeat_count", 1)
if type(repeat_count) is not int or repeat_count < 1:
raise ValueError("循环次数必须为正整数")
cases = payload["cases"]
total = len(cases) * repeat_count
if not cases or total > settings.ARTEMIS_BATCH_MAX_EXECUTIONS:
raise ValueError(
f"请选择用例;用例数 × 循环次数不能超过 {settings.ARTEMIS_BATCH_MAX_EXECUTIONS} 次执行"
)
bid = new_id()
batch = {
"id": bid,
@@ -342,21 +351,28 @@ def create_batch(payload: dict) -> dict:
"timeout_seconds": payload.get("timeout_seconds") or settings.ARTEMIS_TASK_TIMEOUT_SECONDS,
"stop_on_failure": payload.get("stop_on_failure", False),
"request_id": payload.get("request_id"),
"repeat_count": repeat_count,
"case_count": len(cases),
"total_executions": total,
}
jobs = []
for number, case in enumerate(payload["cases"], 1):
jobs.append(
{
"session_id": new_id(),
"batch_id": bid,
"position": number,
"case": case,
"status": "pending",
"formats": batch["formats"],
"created_at": time.time(),
"device_serial": batch["device_serial"],
}
)
for iteration in range(1, repeat_count + 1):
for number, case in enumerate(cases, 1):
jobs.append(
{
"session_id": new_id(),
"batch_id": bid,
"position": len(jobs) + 1,
"iteration": iteration,
"repeat_count": repeat_count,
"case_position": number,
"case": case,
"status": "pending",
"formats": batch["formats"],
"created_at": batch["created_at"],
"device_serial": batch["device_serial"],
}
)
return store.create_batch(batch, jobs, payload)
@@ -412,7 +428,13 @@ def batch_action(batch_id: str, action: str) -> dict:
if not failed:
raise ValueError("没有可重跑的失败或中断任务")
revised = create_batch(
{**batch, "name": batch["name"] + " · 失败项重跑", "cases": [j["case"] for j in failed]}
{
**batch,
"name": batch["name"] + " · 失败项重跑",
"cases": [j["case"] for j in failed],
"repeat_count": 1,
"request_id": None,
}
)
return revised
else:
+4 -2
View File
@@ -22,7 +22,7 @@ from artemis.config import IMAGES_DIR, TRACES_PATH, WORKSPACE_ROOT
from apps.admin_console.database.repositories.session_repository import SessionRepository
from apps.admin_console.database.repositories.step_repository import StepRepository
from apps.admin_console.services.automation_store import store, identifier
from apps.admin_console.services.task_import import case_title
from apps.admin_console.services.task_import import execution_title
EXPORT_ROOT = WORKSPACE_ROOT / "artifacts" / "reports"
EXPORT_LOCK = threading.RLock()
@@ -190,7 +190,9 @@ def session_report(session_id: str, db_path=None) -> dict:
)
return {
"id": session_id,
"name": case_title(case) or session.get("initial_goal", session_id)[:120],
"name": execution_title(meta) or session.get("initial_goal", session_id)[:120],
"iteration": meta.get("iteration", 1),
"repeat_count": meta.get("repeat_count", 1),
"goal": case.get("goal") or session.get("initial_goal", ""),
"expected": case.get("expected", ""),
"status": status,
@@ -189,3 +189,11 @@ def case_title(case: dict) -> str:
case_id = str(case.get("case_id") or "").strip()
name = str(case.get("name") or "").strip()
return " ".join(part for part in (case_id, name) if part)
def execution_title(job: dict) -> str:
"""Distinguish repeated executions without altering the original case or prompt."""
title = case_title(job.get("case") or {})
if job.get("repeat_count", 1) > 1:
title += f" · 第 {job['iteration']} / {job['repeat_count']} 轮"
return title