feat(intelligent-eval): platform triggers OpenClaw agent as worker (avoid external IM channel)
All checks were successful
CI / test (push) Successful in 3m48s
All checks were successful
CI / test (push) Successful in 3m48s
OpenClaw cron requires a channel (announce->last fail-closed); webchat is a
Control-UI feature, not an addressable channel, and platform-side static
execution would degrade the intelligent eval into a static evaluation.
Solution (plan C): the platform keeps the scan loop and, when the queue has
a pending task, invokes the headless agent:
docker exec openclaw-eval openclaw agent --agent main \
-m agenteval-intelligent-worker --json
--deliver defaults to false, so no cron delivery channel is involved. The
worker skill runs unchanged under the OpenClaw agent (LLM decisions +
evaluator/analyst skills). Verified headless invocation returns ok.
This commit is contained in:
parent
5836b84681
commit
775b070bab
@ -31,12 +31,61 @@ from agenteval.web.routers import (
|
|||||||
from agenteval.web.websocket import ws_manager
|
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:
|
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
|
v1.1.0 缺陷修复:`scan_and_enqueue_tasks` 此前没有调度点,OpenClaw Worker
|
||||||
每分钟唤醒却永远取不到任务。平台启动后每 60s 扫描一次 executing 的评估,
|
每分钟唤醒却永远取不到任务。平台启动后每 60s:① 扫描 executing 的评估入队;
|
||||||
与 Worker 的每分钟唤醒对齐。失败不阻断(下次循环继续)。
|
② 若有 pending 任务则触发 OpenClaw agent 执行 worker skill(方案③,免外部
|
||||||
|
channel)。失败不阻断(下次循环继续)。
|
||||||
"""
|
"""
|
||||||
_logger = logging.getLogger("agenteval")
|
_logger = logging.getLogger("agenteval")
|
||||||
while True:
|
while True:
|
||||||
@ -52,6 +101,10 @@ async def _intelligent_eval_scan_loop() -> None:
|
|||||||
session.close()
|
session.close()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logging.getLogger("agenteval").warning("智能评估扫描失败(忽略): %s", 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)
|
await asyncio.sleep(60)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -32,3 +32,49 @@ def test_lifespan_starts_scan_loop(monkeypatch):
|
|||||||
|
|
||||||
# The background task runs immediately (before its first 60s sleep).
|
# The background task runs immediately (before its first 60s sleep).
|
||||||
assert calls, "scan_and_enqueue_tasks should have been invoked on startup"
|
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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user