2 Commits

Author SHA1 Message Date
zhouyuanxi
5c9c80fd72 Stop l112233 waiting validation tasks 2026-07-26 00:02:46 +08:00
zhouyuanxi
f3ef1387ba Use verified waiting task IDs and xc token 2026-07-25 16:29:38 +08:00
2 changed files with 33 additions and 84 deletions

View File

@@ -1,6 +1,6 @@
# xc_validation_strategy_vllm_stop
用于停止 `zhoukaile` 账号当前状态为 `waiting` 的验证任务的 ModelHub XC 策略服务。
用于停止 `l112233` 账号当前状态为 `waiting` 的验证任务的 ModelHub XC 策略服务。
服务启动后会调用:
@@ -11,20 +11,15 @@ PUT /api/async/task/stop-create-contest-task
停止请求完成后,进程继续运行,以便平台通过健康检查和状态接口读取执行结果。
## 必填配置
## 任务清单
策略构建/运行环境中配置以下机密环境变量:
策略内置了 2026-07-25 通过 `l112233` 账号查询得到的 99 个 `waiting` 任务 ID。总历史任务数为 518但已完成与失败任务不会写入该清单也不会被停止。
```text
XC_TOKEN=<zhoukaile 的 xc-Token>
USER_PASSWORD=<zhoukaile 的登录密码>
```
`USER_PASSWORD` 仅用于获得短期 Bearer Token以分页读取该账号实时提交的验证任务`XC_TOKEN` 用于停止接口。两者均不写入仓库。可选配置如下:
可选配置如下:
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `TASK_IDS` | | 可选的逗号分隔任务 ID设置后跳过实时查询,仅停止指定 ID。 |
| `TASK_IDS` | 内置的 99 个 waiting 任务 ID | 可选的逗号分隔任务 ID设置后完全替换内置清单。 |
| `BASE_URL` | `https://modelhub.org.cn` | ModelHub 服务地址。 |
| `BATCH_SIZE` | `50` | 每个停止请求包含的任务数。 |
| `MAX_RETRIES` | `3` | 单批任务的最大请求次数。 |
@@ -38,13 +33,11 @@ USER_PASSWORD=<zhoukaile 的登录密码>
## 运行与构建
仓库根目录的 `Dockerfile` 使用平台 Python 基础镜像,暴露 `8080` 端口并以 `python main.py` 启动。策略构建时请将 `XC_TOKEN``USER_PASSWORD` 都设置为机密变量;构建成功后,策略会先分页读取当前账号的全部任务,仅筛选状态为 `waiting` 的任务再分批停止
仓库根目录的 `Dockerfile` 使用平台 Python 基础镜像,暴露 `8080` 端口并以 `python main.py` 启动。构建成功后,策略会将内置任务 ID 按批调用停止接口
本地验证:
```bash
export XC_TOKEN='***'
export USER_PASSWORD='***'
python main.py
curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/status

98
main.py
View File

@@ -16,12 +16,9 @@ from typing import Any
import requests
BASE_URL = os.environ.get("BASE_URL", "https://modelhub.org.cn").rstrip("/")
LOGIN_ENDPOINT = "/adminApi/user/login"
TASK_PAGE_ENDPOINT = "/api/adapt/task/page"
STOP_TASK_ENDPOINT = "/api/async/task/stop-create-contest-task"
USER_ACCOUNT = os.environ.get("USER_ACCOUNT", "zhoukaile")
USER_PASSWORD = os.environ.get("USER_PASSWORD", "")
XC_TOKEN = os.environ.get("XC_TOKEN", "")
USER_ACCOUNT = "l112233"
XC_TOKEN = "40cb6910dc9a442a816298a228da65ac"
STRATEGY_ID = os.environ.get("STRATEGY_ID", "")
HTTP_HOST = "0.0.0.0"
@@ -30,8 +27,26 @@ BATCH_SIZE = int(os.environ.get("BATCH_SIZE", "50"))
MAX_RETRIES = int(os.environ.get("MAX_RETRIES", "3"))
REQUEST_TIMEOUT = int(os.environ.get("REQUEST_TIMEOUT", "30"))
PAGE_SIZE = 100
STOPPABLE_STATUS = "waiting"
# Snapshot queried from /api/adapt/task/page on 2026-07-25. These are the
# 99 records whose status was exactly "waiting"; completed/failed tasks are
# deliberately excluded.
DEFAULT_TASK_IDS = [
3471434, 3471433, 3471431, 3471430, 3471429, 3471428, 3471427,
3471426, 3471425, 3471424, 3471423, 3471422, 3471421, 3471420,
3471419, 3471418, 3471416, 3471415, 3471414, 3471413, 3471412,
3471411, 3471410, 3471409, 3471408, 3471407, 3471406, 3471405,
3471404, 3471403, 3471402, 3471401, 3471400, 3471399, 3471398,
3471397, 3471396, 3471395, 3471394, 3471393, 3471392, 3471391,
3471390, 3471389, 3471388, 3471387, 3471386, 3471385, 3471384,
3471383, 3471382, 3471381, 3471380, 3471379, 3471378, 3471377,
3471376, 3471375, 3471374, 3471373, 3471372, 3471371, 3471370,
3471369, 3471368, 3471367, 3471366, 3471365, 3471364, 3471363,
3471362, 3471361, 3471360, 3471359, 3471358, 3471357, 3471356,
3471355, 3471354, 3471353, 3471352, 3471351, 3471350, 3471348,
3471347, 3471346, 3471345, 3471344, 3471343, 3471342, 3471341,
3471340, 3471339, 3471338, 3471337, 3471336, 3007636, 3007635,
3007634,
]
_shutdown = threading.Event()
_state: dict[str, Any] = {
@@ -70,65 +85,6 @@ def _task_ids_from_environment() -> list[int] | None:
return list(dict.fromkeys(task_ids))
def _login() -> str:
"""Authenticate as the task owner and return a short-lived bearer token."""
if not USER_PASSWORD:
raise RuntimeError("USER_PASSWORD is required to query zhoukaile's current task IDs")
response = requests.post(
f"{BASE_URL}{LOGIN_ENDPOINT}",
headers={"Content-Type": "application/json"},
json={"userAccount": USER_ACCOUNT, "userPassword": USER_PASSWORD},
timeout=REQUEST_TIMEOUT,
)
response.raise_for_status()
result = response.json()
token = result.get("data", {}).get("token")
if result.get("code") != 0 or not isinstance(token, str) or not token:
raise RuntimeError(f"Task query login failed: {result.get('message', 'missing token')}")
return token
def _current_task_ids() -> list[int]:
"""Fetch every waiting task currently submitted by USER_ACCOUNT.
This intentionally queries the user-facing task page at run time. A static
list becomes obsolete as soon as zhoukaile submits more validation tasks.
"""
bearer_token = _login()
headers = {"Authorization": f"Bearer {bearer_token}"}
task_ids: list[int] = []
page = 1
while True:
response = requests.get(
f"{BASE_URL}{TASK_PAGE_ENDPOINT}",
headers=headers,
params={"current": page, "pageSize": PAGE_SIZE},
timeout=REQUEST_TIMEOUT,
)
response.raise_for_status()
result = response.json()
if result.get("code") != 0:
raise RuntimeError(f"Task query failed: {result.get('message', 'unknown error')}")
data = result.get("data") or {}
records = data.get("records") or []
for record in records:
if str(record.get("status", "")).lower() != STOPPABLE_STATUS:
continue
try:
task_ids.append(int(record["taskId"]))
except (KeyError, TypeError, ValueError):
print(f"[query] skipping record without a numeric taskId: {record}", flush=True)
pages = int(data.get("pages") or 0)
if page >= pages:
break
page += 1
return list(dict.fromkeys(task_ids))
class Handler(BaseHTTPRequestHandler):
def do_GET(self) -> None: # noqa: N802 - BaseHTTPRequestHandler API
if self.path == "/health":
@@ -160,7 +116,10 @@ def _run_http() -> None:
def _stop_batch(task_ids: list[int]) -> bool:
headers = {"Content-Type": "application/json", "xc-Token": XC_TOKEN}
headers = {
"Content-Type": "application/json",
"xc-Token": XC_TOKEN,
}
url = f"{BASE_URL}{STOP_TASK_ENDPOINT}"
for attempt in range(1, MAX_RETRIES + 1):
try:
@@ -197,10 +156,7 @@ def _run_worker() -> None:
_state["started_at"] = _now()
_state["phase"] = "stopping"
try:
if not XC_TOKEN:
raise RuntimeError("XC_TOKEN is required and must be configured in the strategy environment")
task_ids = _task_ids_from_environment() or _current_task_ids()
task_ids = _task_ids_from_environment() or DEFAULT_TASK_IDS
if not task_ids:
print("[query] no active validation tasks found", flush=True)
_state["phase"] = "done"