mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
Fix Agent task relative-time validation (#6146)
This commit is contained in:
@@ -60,13 +60,12 @@ class CreateAgentTaskInput(BaseModel):
|
|||||||
if not self.name or not self.content:
|
if not self.name or not self.content:
|
||||||
raise ValueError("name 和 content 不能只包含空白字符")
|
raise ValueError("name 和 content 不能只包含空白字符")
|
||||||
if self.trigger_type == "date":
|
if self.trigger_type == "date":
|
||||||
if (self.trigger is None) == (self.delay_minutes is None):
|
|
||||||
raise ValueError("date 任务必须且只能提供 trigger 或 delay_minutes 之一")
|
|
||||||
if self.delay_minutes is not None:
|
if self.delay_minutes is not None:
|
||||||
timezone = pytz.timezone(settings.TZ)
|
# LangChain 会在 run() 前后各校验一次,延迟时间在持久化前统一计算。
|
||||||
self.trigger = (
|
self.trigger = None
|
||||||
datetime.now(timezone) + timedelta(minutes=self.delay_minutes)
|
return self
|
||||||
).isoformat(timespec="seconds")
|
if self.trigger is None:
|
||||||
|
raise ValueError("date 任务必须提供 trigger 或 delay_minutes")
|
||||||
elif self.trigger is None or self.delay_minutes is not None:
|
elif self.trigger is None or self.delay_minutes is not None:
|
||||||
raise ValueError("cron 任务必须提供 trigger,且不能提供 delay_minutes")
|
raise ValueError("cron 任务必须提供 trigger,且不能提供 delay_minutes")
|
||||||
self.trigger_type, self.trigger = TimerUtils.normalize_schedule_trigger(
|
self.trigger_type, self.trigger = TimerUtils.normalize_schedule_trigger(
|
||||||
@@ -102,6 +101,18 @@ class CreateAgentTaskTool(MoviePilotTool):
|
|||||||
"""持久化任务并立即注册到运行时调度器。"""
|
"""持久化任务并立即注册到运行时调度器。"""
|
||||||
from app.scheduler import Scheduler
|
from app.scheduler import Scheduler
|
||||||
|
|
||||||
|
trigger_value = payload.trigger
|
||||||
|
if payload.trigger_type == "date" and payload.delay_minutes is not None:
|
||||||
|
timezone = pytz.timezone(settings.TZ)
|
||||||
|
trigger_value = (
|
||||||
|
datetime.now(timezone) + timedelta(minutes=payload.delay_minutes)
|
||||||
|
).isoformat(timespec="seconds")
|
||||||
|
_, trigger_value = TimerUtils.normalize_schedule_trigger(
|
||||||
|
trigger_type=payload.trigger_type,
|
||||||
|
trigger_value=trigger_value,
|
||||||
|
timezone_name=settings.TZ,
|
||||||
|
require_future=True,
|
||||||
|
)
|
||||||
chat = AgentChatOper().get(
|
chat = AgentChatOper().get(
|
||||||
session_id=self._session_id,
|
session_id=self._session_id,
|
||||||
user_id=self._user_id,
|
user_id=self._user_id,
|
||||||
@@ -110,8 +121,8 @@ class CreateAgentTaskTool(MoviePilotTool):
|
|||||||
name=payload.name.strip(),
|
name=payload.name.strip(),
|
||||||
content=payload.content.strip(),
|
content=payload.content.strip(),
|
||||||
trigger_type=payload.trigger_type,
|
trigger_type=payload.trigger_type,
|
||||||
cron_expression=payload.trigger if payload.trigger_type == "cron" else None,
|
cron_expression=trigger_value if payload.trigger_type == "cron" else None,
|
||||||
run_at=payload.trigger if payload.trigger_type == "date" else None,
|
run_at=trigger_value if payload.trigger_type == "date" else None,
|
||||||
user_id=str(self._user_id),
|
user_id=str(self._user_id),
|
||||||
username=self._username or (chat.username if chat else None),
|
username=self._username or (chat.username if chat else None),
|
||||||
session_id=str(self._session_id),
|
session_id=str(self._session_id),
|
||||||
|
|||||||
@@ -61,13 +61,11 @@ class UpdateAgentTaskInput(BaseModel):
|
|||||||
if self.trigger_type is None:
|
if self.trigger_type is None:
|
||||||
raise ValueError("修改触发配置时必须提供 trigger_type")
|
raise ValueError("修改触发配置时必须提供 trigger_type")
|
||||||
if self.trigger_type == "date":
|
if self.trigger_type == "date":
|
||||||
if (self.trigger is None) == (self.delay_minutes is None):
|
|
||||||
raise ValueError("date 任务必须且只能提供 trigger 或 delay_minutes 之一")
|
|
||||||
if self.delay_minutes is not None:
|
if self.delay_minutes is not None:
|
||||||
timezone = pytz.timezone(settings.TZ)
|
# 保持校验幂等,具体绝对时间在更新调度前只计算一次。
|
||||||
self.trigger = (
|
self.trigger = None
|
||||||
datetime.now(timezone) + timedelta(minutes=self.delay_minutes)
|
elif self.trigger is None:
|
||||||
).isoformat(timespec="seconds")
|
raise ValueError("date 任务必须提供 trigger 或 delay_minutes")
|
||||||
elif self.trigger is None or self.delay_minutes is not None:
|
elif self.trigger is None or self.delay_minutes is not None:
|
||||||
raise ValueError("cron 任务必须提供 trigger,且不能提供 delay_minutes")
|
raise ValueError("cron 任务必须提供 trigger,且不能提供 delay_minutes")
|
||||||
if all(
|
if all(
|
||||||
@@ -112,9 +110,16 @@ class UpdateAgentTaskTool(MoviePilotTool):
|
|||||||
return {"error": f"Agent 定时任务 {payload.task_id} 正在执行,请稍后再修改"}
|
return {"error": f"Agent 定时任务 {payload.task_id} 正在执行,请稍后再修改"}
|
||||||
|
|
||||||
trigger_type = payload.trigger_type or task.trigger_type
|
trigger_type = payload.trigger_type or task.trigger_type
|
||||||
trigger_value = payload.trigger or (
|
trigger_value = payload.trigger
|
||||||
task.cron_expression if trigger_type == "cron" else task.run_at
|
if trigger_type == "date" and payload.delay_minutes is not None:
|
||||||
)
|
timezone = pytz.timezone(settings.TZ)
|
||||||
|
trigger_value = (
|
||||||
|
datetime.now(timezone) + timedelta(minutes=payload.delay_minutes)
|
||||||
|
).isoformat(timespec="seconds")
|
||||||
|
if trigger_value is None:
|
||||||
|
trigger_value = (
|
||||||
|
task.cron_expression if trigger_type == "cron" else task.run_at
|
||||||
|
)
|
||||||
enabled = task.enabled if payload.enabled is None else payload.enabled
|
enabled = task.enabled if payload.enabled is None else payload.enabled
|
||||||
normalized_type, normalized_trigger = TimerUtils.normalize_schedule_trigger(
|
normalized_type, normalized_trigger = TimerUtils.normalize_schedule_trigger(
|
||||||
trigger_type=trigger_type,
|
trigger_type=trigger_type,
|
||||||
@@ -128,7 +133,7 @@ class UpdateAgentTaskTool(MoviePilotTool):
|
|||||||
update_payload["name"] = payload.name.strip()
|
update_payload["name"] = payload.name.strip()
|
||||||
if payload.content is not None:
|
if payload.content is not None:
|
||||||
update_payload["content"] = payload.content.strip()
|
update_payload["content"] = payload.content.strip()
|
||||||
if payload.trigger is not None:
|
if payload.trigger_type is not None:
|
||||||
update_payload.update(
|
update_payload.update(
|
||||||
{
|
{
|
||||||
"trigger_type": normalized_type,
|
"trigger_type": normalized_type,
|
||||||
|
|||||||
@@ -195,14 +195,12 @@ async def test_agent_task_tools_manage_persistent_schedule(monkeypatch) -> None:
|
|||||||
monkeypatch.setattr("app.scheduler.Scheduler", lambda: fake_scheduler)
|
monkeypatch.setattr("app.scheduler.Scheduler", lambda: fake_scheduler)
|
||||||
|
|
||||||
create_tool = _build_tool(CreateAgentTaskTool, user_id)
|
create_tool = _build_tool(CreateAgentTaskTool, user_id)
|
||||||
created = json.loads(
|
created = json.loads(await create_tool.ainvoke({
|
||||||
await create_tool.run(
|
"name": "十分钟后检查",
|
||||||
name="十分钟后检查",
|
"content": "检查示例电影是否有资源,不要自动下载",
|
||||||
content="检查示例电影是否有资源,不要自动下载",
|
"trigger_type": "date",
|
||||||
trigger_type="date",
|
"delay_minutes": 10,
|
||||||
delay_minutes=10,
|
}))
|
||||||
)
|
|
||||||
)
|
|
||||||
task_id = created["id"]
|
task_id = created["id"]
|
||||||
assert created["enabled"] is True
|
assert created["enabled"] is True
|
||||||
assert datetime.fromisoformat(created["run_at"]) > datetime.now(
|
assert datetime.fromisoformat(created["run_at"]) > datetime.now(
|
||||||
@@ -217,6 +215,16 @@ async def test_agent_task_tools_manage_persistent_schedule(monkeypatch) -> None:
|
|||||||
assert queried["tasks"][0]["content"].startswith("检查示例电影")
|
assert queried["tasks"][0]["content"].startswith("检查示例电影")
|
||||||
|
|
||||||
update_tool = _build_tool(UpdateAgentTaskTool, user_id)
|
update_tool = _build_tool(UpdateAgentTaskTool, user_id)
|
||||||
|
delayed_update = json.loads(await update_tool.ainvoke({
|
||||||
|
"task_id": task_id,
|
||||||
|
"trigger_type": "date",
|
||||||
|
"delay_minutes": 20,
|
||||||
|
}))
|
||||||
|
assert delayed_update["trigger_type"] == "date"
|
||||||
|
assert datetime.fromisoformat(delayed_update["run_at"]) > datetime.now(
|
||||||
|
pytz.timezone(settings.TZ)
|
||||||
|
)
|
||||||
|
|
||||||
updated = json.loads(
|
updated = json.loads(
|
||||||
await update_tool.run(
|
await update_tool.run(
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user