2026-07-21 18:45:22 +08:00
|
|
|
|
"""
|
2026-07-24 18:53:24 +08:00
|
|
|
|
ModelHub Cancel-All 智能体
|
|
|
|
|
|
启动后自动取消当前账号所有 waiting/running 状态的验证任务
|
|
|
|
|
|
用法:推送此版本到平台,选择对应 tag 运行即可
|
2026-07-21 18:45:22 +08:00
|
|
|
|
"""
|
2026-07-21 19:04:43 +08:00
|
|
|
|
|
2026-07-21 18:45:22 +08:00
|
|
|
|
import json
|
|
|
|
|
|
import os
|
|
|
|
|
|
import signal
|
2026-07-21 19:04:43 +08:00
|
|
|
|
import time
|
2026-07-24 18:53:24 +08:00
|
|
|
|
import threading
|
2026-07-21 19:04:43 +08:00
|
|
|
|
from datetime import datetime
|
2026-07-21 18:45:22 +08:00
|
|
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
2026-07-21 19:04:43 +08:00
|
|
|
|
|
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
# 配置
|
|
|
|
|
|
# ============================================================
|
2026-07-21 18:45:22 +08:00
|
|
|
|
|
|
|
|
|
|
HOST = "0.0.0.0"
|
|
|
|
|
|
PORT = 8080
|
2026-07-21 19:04:43 +08:00
|
|
|
|
|
2026-07-24 18:53:24 +08:00
|
|
|
|
# 目标GPU和Token(跟提交版本保持一致)
|
2026-07-23 23:51:28 +08:00
|
|
|
|
TARGET_GPU = "Iluvatar_bi-150"
|
2026-07-23 23:19:20 +08:00
|
|
|
|
TARGET_TOKEN = "f45f1aae2c094426be237c88b1085015"
|
2026-07-21 19:04:43 +08:00
|
|
|
|
|
|
|
|
|
|
MODELHUB_API = "https://modelhub.org.cn/api"
|
2026-07-24 18:53:24 +08:00
|
|
|
|
BATCH_SIZE = 50 # 每批取消数量(API上限)
|
2026-07-21 19:04:43 +08:00
|
|
|
|
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
# 全局状态
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
|
2026-07-24 18:53:24 +08:00
|
|
|
|
state = {'running': False, 'last_run': None, 'logs': []}
|
2026-07-21 19:04:43 +08:00
|
|
|
|
state_lock = threading.Lock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def log(msg: str):
|
|
|
|
|
|
ts = datetime.now().strftime('%H:%M:%S')
|
|
|
|
|
|
line = f"[{ts}] {msg}"
|
|
|
|
|
|
print(line, flush=True)
|
|
|
|
|
|
with state_lock:
|
|
|
|
|
|
state['logs'].append(line)
|
|
|
|
|
|
if len(state['logs']) > 500:
|
|
|
|
|
|
state['logs'] = state['logs'][-300:]
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-24 18:51:51 +08:00
|
|
|
|
def cancel_all_tasks():
|
|
|
|
|
|
"""取消所有 waiting 和 running 状态的任务"""
|
|
|
|
|
|
headers = {
|
|
|
|
|
|
'Xc-Token': TARGET_TOKEN,
|
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log("=" * 50)
|
|
|
|
|
|
log("开始取消所有任务")
|
|
|
|
|
|
log(f"目标GPU: {TARGET_GPU}")
|
|
|
|
|
|
|
2026-07-24 18:51:51 +08:00
|
|
|
|
# 1. 查询所有任务
|
|
|
|
|
|
all_tasks = []
|
|
|
|
|
|
page = 1
|
|
|
|
|
|
while True:
|
|
|
|
|
|
try:
|
|
|
|
|
|
resp = requests.get(
|
|
|
|
|
|
f"{MODELHUB_API}/adapt/task/page",
|
|
|
|
|
|
headers={'Xc-Token': TARGET_TOKEN, 'Accept': 'application/json'},
|
2026-07-24 18:53:24 +08:00
|
|
|
|
params={'current': page, 'pageSize': 100, 'onlyMine': 'true',
|
|
|
|
|
|
'gpuType': TARGET_GPU},
|
2026-07-24 18:51:51 +08:00
|
|
|
|
timeout=15
|
|
|
|
|
|
)
|
|
|
|
|
|
data = resp.json()
|
|
|
|
|
|
if data.get('code') != 0:
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f"查询失败: code={data.get('code')}")
|
2026-07-24 18:51:51 +08:00
|
|
|
|
break
|
|
|
|
|
|
records = data['data'].get('records', [])
|
|
|
|
|
|
if not records:
|
|
|
|
|
|
break
|
|
|
|
|
|
all_tasks.extend(records)
|
2026-07-24 18:53:24 +08:00
|
|
|
|
total = data['data'].get('total', 0)
|
|
|
|
|
|
log(f" 第{page}页: {len(records)} 条, 累计 {len(all_tasks)} / {total}")
|
|
|
|
|
|
if len(all_tasks) >= int(total):
|
2026-07-24 18:51:51 +08:00
|
|
|
|
break
|
|
|
|
|
|
page += 1
|
|
|
|
|
|
except Exception as e:
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f" 查询异常: {e}")
|
2026-07-24 18:51:51 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 筛选 waiting/running 状态
|
|
|
|
|
|
cancellable = [t for t in all_tasks if t.get('status') in ('waiting', 'running')]
|
|
|
|
|
|
if not cancellable:
|
|
|
|
|
|
log(f" 没有可取消的任务 (总任务 {len(all_tasks)} 个)")
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log("=" * 50)
|
2026-07-24 18:51:51 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
task_ids = [int(t.get('taskId')) for t in cancellable if t.get('taskId')]
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f" 总任务 {len(all_tasks)} 个, 可取消 {len(task_ids)} 个")
|
2026-07-24 18:51:51 +08:00
|
|
|
|
|
2026-07-24 18:53:24 +08:00
|
|
|
|
# 3. 分批取消
|
2026-07-24 18:51:51 +08:00
|
|
|
|
url = f"{MODELHUB_API}/async/task/stop-create-contest-task"
|
|
|
|
|
|
cancelled = 0
|
2026-07-24 18:53:24 +08:00
|
|
|
|
for i in range(0, len(task_ids), BATCH_SIZE):
|
|
|
|
|
|
batch = task_ids[i:i + BATCH_SIZE]
|
2026-07-24 18:51:51 +08:00
|
|
|
|
try:
|
|
|
|
|
|
resp = requests.put(url, headers=headers, json={'taskIds': batch}, timeout=15)
|
|
|
|
|
|
data = resp.json()
|
|
|
|
|
|
if data.get('code') == 0:
|
|
|
|
|
|
cancelled += len(batch)
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f" 批次 {i // BATCH_SIZE + 1}: 取消 {len(batch)} 个 OK")
|
2026-07-24 18:51:51 +08:00
|
|
|
|
else:
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f" 批次 {i // BATCH_SIZE + 1}: 失败 code={data.get('code')} msg={data.get('message', '')[:80]}")
|
2026-07-24 18:51:51 +08:00
|
|
|
|
except Exception as e:
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f" 批次 {i // BATCH_SIZE + 1}: 异常 {e}")
|
2026-07-24 18:51:51 +08:00
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f" 取消完成: {cancelled} / {len(task_ids)}")
|
2026-07-21 19:04:43 +08:00
|
|
|
|
log("=" * 50)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
# HTTP 服务
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
|
|
|
|
|
|
class AgentHandler(BaseHTTPRequestHandler):
|
|
|
|
|
|
def do_GET(self):
|
2026-07-24 18:53:24 +08:00
|
|
|
|
if self.path == '/health':
|
2026-07-21 19:04:43 +08:00
|
|
|
|
self._json({'status': 'ok'})
|
2026-07-24 18:53:24 +08:00
|
|
|
|
elif self.path == '/':
|
2026-07-21 19:04:43 +08:00
|
|
|
|
self._json({
|
2026-07-24 18:53:24 +08:00
|
|
|
|
'name': 'modelhub-cancel-all',
|
|
|
|
|
|
'gpu': TARGET_GPU,
|
2026-07-21 19:04:43 +08:00
|
|
|
|
'status': 'running' if state['running'] else 'idle',
|
2026-07-24 18:53:24 +08:00
|
|
|
|
'last_result': state.get('last_result'),
|
2026-07-21 18:45:22 +08:00
|
|
|
|
})
|
2026-07-24 18:53:24 +08:00
|
|
|
|
elif self.path.startswith('/logs'):
|
|
|
|
|
|
lines = 100
|
2026-07-21 19:04:43 +08:00
|
|
|
|
self._json({'logs': state['logs'][-lines:]})
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._json({'error': 'not found'}, 404)
|
|
|
|
|
|
|
|
|
|
|
|
def _json(self, body: dict, status: int = 200):
|
|
|
|
|
|
payload = json.dumps(body, ensure_ascii=False).encode()
|
2026-07-21 18:45:22 +08:00
|
|
|
|
self.send_response(status)
|
2026-07-21 19:04:43 +08:00
|
|
|
|
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
|
|
|
|
|
self.send_header('Content-Length', str(len(payload)))
|
2026-07-21 18:45:22 +08:00
|
|
|
|
self.end_headers()
|
|
|
|
|
|
self.wfile.write(payload)
|
|
|
|
|
|
|
2026-07-21 19:04:43 +08:00
|
|
|
|
def log_message(self, fmt, *args):
|
2026-07-24 18:53:24 +08:00
|
|
|
|
pass
|
2026-07-21 19:04:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
# 启动
|
|
|
|
|
|
# ============================================================
|
2026-07-21 18:45:22 +08:00
|
|
|
|
|
2026-07-21 19:04:43 +08:00
|
|
|
|
shutdown_requested = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_signal(signum, _frame):
|
2026-07-21 18:45:22 +08:00
|
|
|
|
global shutdown_requested
|
|
|
|
|
|
shutdown_requested = True
|
2026-07-21 19:04:43 +08:00
|
|
|
|
log(f"收到信号 {signum},准备关闭")
|
2026-07-21 18:45:22 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-21 19:04:43 +08:00
|
|
|
|
def main():
|
2026-07-21 18:45:22 +08:00
|
|
|
|
signal.signal(signal.SIGTERM, _handle_signal)
|
|
|
|
|
|
signal.signal(signal.SIGINT, _handle_signal)
|
|
|
|
|
|
|
2026-07-21 19:04:43 +08:00
|
|
|
|
server = ThreadingHTTPServer((HOST, PORT), AgentHandler)
|
2026-07-21 18:45:22 +08:00
|
|
|
|
server.timeout = 1
|
|
|
|
|
|
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f"Cancel-All 智能体启动 | {HOST}:{PORT}")
|
2026-07-23 23:19:20 +08:00
|
|
|
|
log(f"目标GPU: {TARGET_GPU}")
|
2026-07-21 18:45:22 +08:00
|
|
|
|
|
2026-07-24 18:53:24 +08:00
|
|
|
|
# 启动后自动取消所有任务
|
|
|
|
|
|
def _auto_cancel():
|
2026-07-21 20:31:23 +08:00
|
|
|
|
time.sleep(2)
|
2026-07-21 20:45:26 +08:00
|
|
|
|
try:
|
2026-07-24 18:53:24 +08:00
|
|
|
|
state['running'] = True
|
|
|
|
|
|
state['last_run'] = datetime.now().isoformat()
|
2026-07-24 18:51:51 +08:00
|
|
|
|
cancel_all_tasks()
|
2026-07-24 18:53:24 +08:00
|
|
|
|
state['last_result'] = {'success': True}
|
2026-07-21 20:45:26 +08:00
|
|
|
|
except Exception as e:
|
2026-07-24 18:53:24 +08:00
|
|
|
|
log(f"异常: {e}")
|
|
|
|
|
|
state['last_result'] = {'error': str(e)}
|
|
|
|
|
|
finally:
|
|
|
|
|
|
state['running'] = False
|
2026-07-21 20:45:26 +08:00
|
|
|
|
|
2026-07-24 18:53:24 +08:00
|
|
|
|
threading.Thread(target=_auto_cancel, daemon=True).start()
|
2026-07-21 19:09:41 +08:00
|
|
|
|
|
2026-07-21 18:45:22 +08:00
|
|
|
|
while not shutdown_requested:
|
|
|
|
|
|
server.handle_request()
|
|
|
|
|
|
|
|
|
|
|
|
server.server_close()
|
2026-07-21 19:04:43 +08:00
|
|
|
|
log("智能体已关闭")
|
2026-07-21 18:45:22 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-21 19:04:43 +08:00
|
|
|
|
if __name__ == '__main__':
|
2026-07-21 18:45:22 +08:00
|
|
|
|
main()
|