diff --git a/backend/agenteval/web/app.py b/backend/agenteval/web/app.py index 1039251..b8d9a78 100644 --- a/backend/agenteval/web/app.py +++ b/backend/agenteval/web/app.py @@ -31,12 +31,61 @@ from agenteval.web.routers import ( from agenteval.web.websocket import ws_manager +def _has_pending_task() -> bool: + """True if the intelligent-eval task queue has a pending worker task.""" + from agenteval.intelligent_eval.task_queue import get_next_task + + session = get_session() + try: + return get_next_task(session) is not None + finally: + session.close() + + +async def _trigger_intelligent_worker() -> bool: + """Trigger OpenClaw's headless agent to run the intelligent-eval worker skill. + + 方案③(避免外部 IM channel):平台只当"触发闹钟"——有 pending 任务时, + 用 `docker exec openclaw-eval openclaw agent --agent main -m + "agenteval-intelligent-worker"` 唤醒 agent,worker skill 由 OpenClaw agent + 自主执行(决策/建会话/真实对话/上报,逻辑零改动)。`--deliver` 默认 false + 因此不经 cron delivery channel。 + + Returns: + True 若确实触发了 agent(存在 pending 任务)。 + """ + if not _has_pending_task(): + return False + + import subprocess + + _logger = logging.getLogger("agenteval") + try: + proc = await asyncio.to_thread( + subprocess.run, + [ + "docker", "exec", "openclaw-eval", "openclaw", "agent", + "--agent", "main", "-m", "agenteval-intelligent-worker", "--json", + ], + capture_output=True, + text=True, + timeout=300, + ) + _logger.info("Worker 触发完成 exit=%s", proc.returncode) + if proc.returncode != 0: + _logger.warning("Worker 触发 stderr: %s", proc.stderr[-300:]) + except Exception as exc: + _logger.warning("Worker 触发失败(忽略): %s", exc) + return True + + async def _intelligent_eval_scan_loop() -> None: - """Scan executing intelligent evals and enqueue worker tasks every minute. + """Scan executing intelligent evals, enqueue tasks, and trigger the worker. v1.1.0 缺陷修复:`scan_and_enqueue_tasks` 此前没有调度点,OpenClaw Worker - 每分钟唤醒却永远取不到任务。平台启动后每 60s 扫描一次 executing 的评估, - 与 Worker 的每分钟唤醒对齐。失败不阻断(下次循环继续)。 + 每分钟唤醒却永远取不到任务。平台启动后每 60s:① 扫描 executing 的评估入队; + ② 若有 pending 任务则触发 OpenClaw agent 执行 worker skill(方案③,免外部 + channel)。失败不阻断(下次循环继续)。 """ _logger = logging.getLogger("agenteval") while True: @@ -52,6 +101,10 @@ async def _intelligent_eval_scan_loop() -> None: session.close() except Exception as exc: logging.getLogger("agenteval").warning("智能评估扫描失败(忽略): %s", exc) + try: + await _trigger_intelligent_worker() + except Exception as exc: + logging.getLogger("agenteval").warning("Worker 触发失败(忽略): %s", exc) await asyncio.sleep(60) diff --git a/tests/integration/test_intelligent_eval_scan_scheduler.py b/tests/integration/test_intelligent_eval_scan_scheduler.py index e0c28bc..5117ced 100644 --- a/tests/integration/test_intelligent_eval_scan_scheduler.py +++ b/tests/integration/test_intelligent_eval_scan_scheduler.py @@ -32,3 +32,49 @@ def test_lifespan_starts_scan_loop(monkeypatch): # The background task runs immediately (before its first 60s sleep). assert calls, "scan_and_enqueue_tasks should have been invoked on startup" + + +def test_trigger_worker_skips_when_no_pending(monkeypatch): + """No pending task → no docker exec invocation.""" + import asyncio + import subprocess + from unittest.mock import MagicMock + + import agenteval.web.app as app_mod + + calls: list = [] + monkeypatch.setattr(app_mod, "_has_pending_task", lambda: False) + + def fake_run(cmd, **kwargs): + calls.append(cmd) + return MagicMock(returncode=0, stderr="") + + monkeypatch.setattr(subprocess, "run", fake_run) + + assert asyncio.run(app_mod._trigger_intelligent_worker()) is False + assert not calls, "should not invoke docker exec when queue is empty" + + +def test_trigger_worker_calls_docker_exec(monkeypatch): + """Pending task present → invoke `docker exec openclaw-eval openclaw agent`.""" + import asyncio + import subprocess + from unittest.mock import MagicMock + + import agenteval.web.app as app_mod + + calls: list = [] + monkeypatch.setattr(app_mod, "_has_pending_task", lambda: True) + + def fake_run(cmd, **kwargs): + calls.append(cmd) + return MagicMock(returncode=0, stderr="") + + monkeypatch.setattr(subprocess, "run", fake_run) + + assert asyncio.run(app_mod._trigger_intelligent_worker()) is True + assert calls, "docker exec should be invoked" + joined = " ".join(calls[0]) + assert "docker" in joined and "openclaw" in joined + assert "agenteval-intelligent-worker" in joined + assert "--agent" in joined and "main" in joined