第一次提交
This commit is contained in:
116
app-instance/backend/nanobot/agent/tools/cron_action.py
Normal file
116
app-instance/backend/nanobot/agent/tools/cron_action.py
Normal file
@ -0,0 +1,116 @@
|
||||
"""结构化 cron 生命周期控制工具。
|
||||
|
||||
cron 任务不是普通用户对话,它经常需要在运行完成后主动告诉调度器:
|
||||
- 这个任务已经可以删掉;
|
||||
- 今天这一轮先结束,下一天再继续;
|
||||
- 下次应该改成新的时间表。
|
||||
|
||||
这个工具就是让模型把这些决策显式写成结构化数据,而不是只留在自然语言里。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from nanobot.agent.tools.base import Tool
|
||||
from nanobot.cron.types import CronAction
|
||||
|
||||
|
||||
class CronActionTool(Tool):
|
||||
"""捕获模型输出的机器可读 cron 控制决策。"""
|
||||
|
||||
def __init__(self, job_id: str):
|
||||
# `job_id` 仅用于回显和审计,不参与决策本身。
|
||||
self.job_id = job_id
|
||||
# `_decision` 在本轮 agent 执行期间最多被写一次,外部在结束后读取。
|
||||
self._decision: CronAction | None = None
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "cron_action"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return "Record a structured lifecycle action for the currently running cron job."
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict[str, Any]:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["none", "remove", "disable", "complete_today", "reschedule"],
|
||||
"description": "Lifecycle action for the current cron job",
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Short reason for audit logs",
|
||||
},
|
||||
"every_seconds": {
|
||||
"type": "integer",
|
||||
"description": "Required when action=reschedule and using fixed interval",
|
||||
},
|
||||
"cron_expr": {
|
||||
"type": "string",
|
||||
"description": "Required when action=reschedule and using cron expression",
|
||||
},
|
||||
"tz": {
|
||||
"type": "string",
|
||||
"description": "Optional timezone for cron_expr reschedules",
|
||||
},
|
||||
"at": {
|
||||
"type": "string",
|
||||
"description": "Required when action=reschedule and using one-time ISO datetime",
|
||||
},
|
||||
},
|
||||
"required": ["action"],
|
||||
}
|
||||
|
||||
@property
|
||||
def decision(self) -> CronAction | None:
|
||||
# 暴露最终结构化决策给 cron runtime,便于后处理调度状态。
|
||||
return self._decision
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
action: str,
|
||||
reason: str | None = None,
|
||||
every_seconds: int | None = None,
|
||||
cron_expr: str | None = None,
|
||||
tz: str | None = None,
|
||||
at: str | None = None,
|
||||
**_kwargs: Any,
|
||||
) -> str:
|
||||
# 统一做小写规范化,避免模型传入 `Remove` / `REMOVE` 之类大小写变体。
|
||||
normalized = (action or "").strip().lower()
|
||||
allowed_actions = {"none", "remove", "disable", "complete_today", "reschedule"}
|
||||
if normalized not in allowed_actions:
|
||||
return f"Error: unsupported cron action '{action}'"
|
||||
# 非重排任务不允许额外携带调度字段,避免出现“说 remove 但又传 cron_expr”的脏数据。
|
||||
if normalized != "reschedule" and any(value is not None for value in (every_seconds, cron_expr, tz, at)):
|
||||
return "Error: schedule fields can only be used when action='reschedule'"
|
||||
|
||||
if normalized == "reschedule":
|
||||
# 重新排期必须在三种时间表达方式里三选一,不能都不传,也不能混传。
|
||||
options = int(every_seconds is not None) + int(bool(cron_expr)) + int(bool(at))
|
||||
if options != 1:
|
||||
return "Error: reschedule requires exactly one of every_seconds, cron_expr, or at"
|
||||
# 时区只有 cron 表达式才有意义。
|
||||
if tz and not cron_expr:
|
||||
return "Error: tz can only be used with cron_expr"
|
||||
|
||||
# 校验通过后,把本轮决策固化为 dataclass,交给 runtime 在执行后统一消费。
|
||||
self._decision = CronAction(
|
||||
action=normalized or "none",
|
||||
reason=(reason or "").strip() or None,
|
||||
every_seconds=every_seconds,
|
||||
cron_expr=cron_expr,
|
||||
tz=tz,
|
||||
at=at,
|
||||
)
|
||||
# 返回给模型/日志的是一条可读确认文本,方便工具调用结果出现在上下文里。
|
||||
detail = f" for job {self.job_id}"
|
||||
if self._decision.reason:
|
||||
detail += f" ({self._decision.reason})"
|
||||
return f"Recorded cron_action={self._decision.action}{detail}"
|
||||
Reference in New Issue
Block a user