feat: add 8 builtin tools, AgentSchedules management page, Celery Beat integration
- Add 3 schedule tools (create/list/delete) and 5 utility tools (crypto, random, email, URL, regex) - Add frontend AgentSchedules.vue page with full CRUD, cron presets, manual trigger - Integrate Celery Beat for automatic schedule execution - Update startup scripts with Celery Beat launch - Fix schedule list API to show all schedules for admin users - Add celrybeat-schedule.* to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -72,12 +72,10 @@ async def list_schedules(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取当前用户的所有定时任务。"""
|
||||
schedules = (
|
||||
db.query(AgentSchedule)
|
||||
.filter(AgentSchedule.user_id == current_user.id)
|
||||
.order_by(AgentSchedule.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
query = db.query(AgentSchedule)
|
||||
if current_user.role != "admin":
|
||||
query = query.filter(AgentSchedule.user_id == current_user.id)
|
||||
schedules = query.order_by(AgentSchedule.created_at.desc()).all()
|
||||
return schedules
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
_registered = False
|
||||
|
||||
_EXPECTED_BUILTIN = 10
|
||||
_EXPECTED_BUILTIN = 18
|
||||
|
||||
|
||||
def ensure_builtin_tools_registered() -> None:
|
||||
@@ -28,6 +28,14 @@ def ensure_builtin_tools_registered() -> None:
|
||||
json_process_tool,
|
||||
database_query_tool,
|
||||
adb_log_tool,
|
||||
schedule_create_tool,
|
||||
schedule_list_tool,
|
||||
schedule_delete_tool,
|
||||
crypto_util_tool,
|
||||
random_generate_tool,
|
||||
send_email_tool,
|
||||
url_parse_tool,
|
||||
regex_test_tool,
|
||||
HTTP_REQUEST_SCHEMA,
|
||||
FILE_READ_SCHEMA,
|
||||
FILE_WRITE_SCHEMA,
|
||||
@@ -38,6 +46,14 @@ def ensure_builtin_tools_registered() -> None:
|
||||
JSON_PROCESS_SCHEMA,
|
||||
DATABASE_QUERY_SCHEMA,
|
||||
ADB_LOG_SCHEMA,
|
||||
SCHEDULE_CREATE_SCHEMA,
|
||||
SCHEDULE_LIST_SCHEMA,
|
||||
SCHEDULE_DELETE_SCHEMA,
|
||||
CRYPTO_UTIL_SCHEMA,
|
||||
RANDOM_GENERATE_SCHEMA,
|
||||
SEND_EMAIL_SCHEMA,
|
||||
URL_PARSE_SCHEMA,
|
||||
REGEX_TEST_SCHEMA,
|
||||
)
|
||||
|
||||
tool_registry.register_builtin_tool("http_request", http_request_tool, HTTP_REQUEST_SCHEMA)
|
||||
@@ -50,6 +66,14 @@ def ensure_builtin_tools_registered() -> None:
|
||||
tool_registry.register_builtin_tool("json_process", json_process_tool, JSON_PROCESS_SCHEMA)
|
||||
tool_registry.register_builtin_tool("database_query", database_query_tool, DATABASE_QUERY_SCHEMA)
|
||||
tool_registry.register_builtin_tool("adb_log", adb_log_tool, ADB_LOG_SCHEMA)
|
||||
tool_registry.register_builtin_tool("schedule_create", schedule_create_tool, SCHEDULE_CREATE_SCHEMA)
|
||||
tool_registry.register_builtin_tool("schedule_list", schedule_list_tool, SCHEDULE_LIST_SCHEMA)
|
||||
tool_registry.register_builtin_tool("schedule_delete", schedule_delete_tool, SCHEDULE_DELETE_SCHEMA)
|
||||
tool_registry.register_builtin_tool("crypto_util", crypto_util_tool, CRYPTO_UTIL_SCHEMA)
|
||||
tool_registry.register_builtin_tool("random_generate", random_generate_tool, RANDOM_GENERATE_SCHEMA)
|
||||
tool_registry.register_builtin_tool("send_email", send_email_tool, SEND_EMAIL_SCHEMA)
|
||||
tool_registry.register_builtin_tool("url_parse", url_parse_tool, URL_PARSE_SCHEMA)
|
||||
tool_registry.register_builtin_tool("regex_test", regex_test_tool, REGEX_TEST_SCHEMA)
|
||||
_registered = True
|
||||
|
||||
n = tool_registry.builtin_tool_count()
|
||||
|
||||
@@ -1343,3 +1343,804 @@ DATABASE_QUERY_SCHEMA = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ─── 加密/哈希/编码工具 ──────────────────────────────────
|
||||
|
||||
async def crypto_util_tool(
|
||||
operation: str = "uuid",
|
||||
data: Optional[str] = None,
|
||||
algorithm: str = "sha256",
|
||||
) -> str:
|
||||
"""
|
||||
加密/哈希/编码工具。
|
||||
|
||||
Args:
|
||||
operation: 操作类型
|
||||
- uuid: 生成 UUID
|
||||
- base64_encode: Base64 编码
|
||||
- base64_decode: Base64 解码
|
||||
- hash: 计算哈希值
|
||||
data: 输入数据(base64/hash 操作需要)
|
||||
algorithm: 哈希算法(hash 操作时使用,支持 md5/sha1/sha256/sha512)
|
||||
|
||||
Returns:
|
||||
操作结果
|
||||
"""
|
||||
import hashlib
|
||||
import base64
|
||||
import uuid as _uuid
|
||||
|
||||
try:
|
||||
if operation == "uuid":
|
||||
u = str(_uuid.uuid4())
|
||||
return json.dumps({"uuid": u, "type": "uuid4"}, ensure_ascii=False)
|
||||
|
||||
elif operation == "base64_encode":
|
||||
if not data:
|
||||
return json.dumps({"error": "data 参数不能为空"}, ensure_ascii=False)
|
||||
encoded = base64.b64encode(data.encode("utf-8")).decode("utf-8")
|
||||
return json.dumps({"encoded": encoded}, ensure_ascii=False)
|
||||
|
||||
elif operation == "base64_decode":
|
||||
if not data:
|
||||
return json.dumps({"error": "data 参数不能为空"}, ensure_ascii=False)
|
||||
try:
|
||||
decoded = base64.b64decode(data.encode("utf-8")).decode("utf-8")
|
||||
return json.dumps({"decoded": decoded}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return json.dumps({"error": f"Base64 解码失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
elif operation == "hash":
|
||||
if not data:
|
||||
return json.dumps({"error": "data 参数不能为空"}, ensure_ascii=False)
|
||||
valid_algos = {"md5": hashlib.md5, "sha1": hashlib.sha1,
|
||||
"sha256": hashlib.sha256, "sha512": hashlib.sha512}
|
||||
if algorithm not in valid_algos:
|
||||
return json.dumps(
|
||||
{"error": f"不支持的算法: {algorithm},支持: {list(valid_algos.keys())}"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
h = valid_algos[algorithm](data.encode("utf-8")).hexdigest()
|
||||
return json.dumps({"algorithm": algorithm, "hash": h}, ensure_ascii=False)
|
||||
|
||||
else:
|
||||
return json.dumps(
|
||||
{"error": f"不支持的操作: {operation},支持: uuid/base64_encode/base64_decode/hash"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"crypto_util 工具执行失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"操作失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
# ─── 随机生成工具 ─────────────────────────────────────────
|
||||
|
||||
async def random_generate_tool(
|
||||
generate_type: str = "password",
|
||||
length: int = 16,
|
||||
count: int = 1,
|
||||
min_val: float = 0,
|
||||
max_val: float = 100,
|
||||
) -> str:
|
||||
"""
|
||||
随机数据生成工具。
|
||||
|
||||
Args:
|
||||
generate_type: 生成类型
|
||||
- password: 安全密码(含大小写字母+数字+符号)
|
||||
- string: 随机字符串(字母+数字)
|
||||
- number: 随机整数
|
||||
- float: 随机浮点数
|
||||
length: 密码/字符串长度(默认16)
|
||||
count: 生成数量(默认1)
|
||||
min_val: 随机数最小值(number/float 时使用)
|
||||
max_val: 随机数最大值(number/float 时使用)
|
||||
|
||||
Returns:
|
||||
生成的随机数据
|
||||
"""
|
||||
import random
|
||||
import string as _string
|
||||
import secrets
|
||||
|
||||
try:
|
||||
count = max(1, min(count, 50)) # 限制最多 50 个
|
||||
|
||||
if generate_type == "password":
|
||||
length = max(8, min(length, 128))
|
||||
chars = _string.ascii_letters + _string.digits + "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
||||
result = []
|
||||
for _ in range(count):
|
||||
pwd = "".join(secrets.choice(chars) for _ in range(length))
|
||||
result.append(pwd)
|
||||
return json.dumps({
|
||||
"passwords": result,
|
||||
"length": length,
|
||||
"strength": "high" if length >= 16 else "medium" if length >= 12 else "basic",
|
||||
}, ensure_ascii=False)
|
||||
|
||||
elif generate_type == "string":
|
||||
length = max(1, min(length, 256))
|
||||
chars = _string.ascii_letters + _string.digits
|
||||
result = ["".join(secrets.choice(chars) for _ in range(length)) for _ in range(count)]
|
||||
return json.dumps({"strings": result, "length": length}, ensure_ascii=False)
|
||||
|
||||
elif generate_type == "number":
|
||||
min_val, max_val = int(min_val), int(max_val)
|
||||
if min_val > max_val:
|
||||
min_val, max_val = max_val, min_val
|
||||
result = [secrets.randbelow(max_val - min_val + 1) + min_val for _ in range(count)]
|
||||
return json.dumps({"numbers": result, "range": [min_val, max_val]}, ensure_ascii=False)
|
||||
|
||||
elif generate_type == "float":
|
||||
if min_val > max_val:
|
||||
min_val, max_val = max_val, min_val
|
||||
result = [round(random.uniform(min_val, max_val), 6) for _ in range(count)]
|
||||
return json.dumps({"floats": result, "range": [min_val, max_val]}, ensure_ascii=False)
|
||||
|
||||
else:
|
||||
return json.dumps(
|
||||
{"error": f"不支持的类型: {generate_type},支持: password/string/number/float"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"random_generate 工具执行失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"生成失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
# ─── 邮件发送工具 ─────────────────────────────────────────
|
||||
|
||||
async def send_email_tool(
|
||||
to: str,
|
||||
subject: str,
|
||||
body: str,
|
||||
smtp_host: str = "",
|
||||
smtp_port: int = 587,
|
||||
smtp_user: str = "",
|
||||
smtp_password: str = "",
|
||||
from_name: str = "",
|
||||
body_type: str = "plain",
|
||||
) -> str:
|
||||
"""
|
||||
发送邮件工具。
|
||||
|
||||
Args:
|
||||
to: 收件人邮箱地址(多个用逗号分隔)
|
||||
subject: 邮件主题
|
||||
body: 邮件正文
|
||||
smtp_host: SMTP 服务器地址(如 smtp.qq.com),留空从环境变量读取
|
||||
smtp_port: SMTP 端口(默认 587)
|
||||
smtp_user: SMTP 用户名/发件人邮箱,留空从环境变量读取
|
||||
smtp_password: SMTP 密码/授权码,留空从环境变量读取
|
||||
from_name: 发件人显示名称(可选)
|
||||
body_type: 正文类型 plain=纯文本, html=HTML
|
||||
|
||||
Returns:
|
||||
发送结果
|
||||
"""
|
||||
try:
|
||||
# 从环境变量读取 SMTP 配置(如果未传参)
|
||||
import os
|
||||
host = smtp_host or os.getenv("SMTP_HOST", "")
|
||||
user = smtp_user or os.getenv("SMTP_USER", "")
|
||||
password = smtp_password or os.getenv("SMTP_PASSWORD", "")
|
||||
|
||||
if not host or not user or not password:
|
||||
return json.dumps({
|
||||
"error": "SMTP 配置不完整。请在 .env 中设置 SMTP_HOST/SMTP_USER/SMTP_PASSWORD,或在调用时传入 smtp_host/smtp_user/smtp_password 参数",
|
||||
}, ensure_ascii=False)
|
||||
|
||||
import aiosmtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
recipients = [r.strip() for r in to.split(",") if r.strip()]
|
||||
if not recipients:
|
||||
return json.dumps({"error": "收件人地址不能为空"}, ensure_ascii=False)
|
||||
|
||||
msg = MIMEMultipart()
|
||||
msg["From"] = f"{from_name} <{user}>" if from_name else user
|
||||
msg["To"] = ", ".join(recipients)
|
||||
msg["Subject"] = subject
|
||||
msg.attach(MIMEText(body, body_type if body_type == "html" else "plain", "utf-8"))
|
||||
|
||||
await aiosmtplib.send(
|
||||
msg,
|
||||
hostname=host,
|
||||
port=smtp_port,
|
||||
username=user,
|
||||
password=password,
|
||||
start_tls=True,
|
||||
)
|
||||
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"message": f"邮件已发送到 {len(recipients)} 个收件人",
|
||||
"recipients": recipients,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"send_email 工具执行失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"邮件发送失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
# ─── URL 解析工具 ─────────────────────────────────────────
|
||||
|
||||
async def url_parse_tool(
|
||||
url: str,
|
||||
operation: str = "parse",
|
||||
key: Optional[str] = None,
|
||||
params: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
URL 解析和构建工具。
|
||||
|
||||
Args:
|
||||
url: 要处理的 URL
|
||||
operation: 操作类型
|
||||
- parse: 解析 URL 为各部分(协议/主机/端口/路径/参数/片段)
|
||||
- get_param: 获取指定查询参数的值(需传 key)
|
||||
- all_params: 获取所有查询参数
|
||||
- build: 构建 URL(url=基础URL, params=要追加的参数 JSON)
|
||||
key: 查询参数名(operation=get_param 时使用)
|
||||
params: 参数 JSON 字符串(operation=build 时使用,如 {"page": "1", "size": "10"})
|
||||
|
||||
Returns:
|
||||
处理结果
|
||||
"""
|
||||
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
|
||||
|
||||
try:
|
||||
if operation == "parse":
|
||||
parsed = urlparse(url)
|
||||
return json.dumps({
|
||||
"scheme": parsed.scheme,
|
||||
"hostname": parsed.hostname,
|
||||
"port": parsed.port,
|
||||
"path": parsed.path,
|
||||
"params": parsed.params,
|
||||
"query": parsed.query,
|
||||
"fragment": parsed.fragment,
|
||||
}, ensure_ascii=False)
|
||||
|
||||
elif operation == "get_param":
|
||||
if not key:
|
||||
return json.dumps({"error": "需要提供 key 参数"}, ensure_ascii=False)
|
||||
parsed = urlparse(url)
|
||||
query_params = parse_qs(parsed.query)
|
||||
val = query_params.get(key, [None])[0]
|
||||
return json.dumps({"key": key, "value": val}, ensure_ascii=False)
|
||||
|
||||
elif operation == "all_params":
|
||||
parsed = urlparse(url)
|
||||
query_params = parse_qs(parsed.query)
|
||||
# parse_qs 返回 {key: [value1, value2]},简化为单值
|
||||
flat = {k: v[0] if len(v) == 1 else v for k, v in query_params.items()}
|
||||
return json.dumps({"params": flat}, ensure_ascii=False)
|
||||
|
||||
elif operation == "build":
|
||||
parsed = urlparse(url)
|
||||
existing = parse_qs(parsed.query, keep_blank_values=True)
|
||||
if params:
|
||||
try:
|
||||
extra = json.loads(params)
|
||||
if isinstance(extra, dict):
|
||||
existing.update(extra)
|
||||
except json.JSONDecodeError as e:
|
||||
return json.dumps({"error": f"params JSON 解析失败: {e}"}, ensure_ascii=False)
|
||||
new_query = urlencode(existing, doseq=True)
|
||||
new_parsed = parsed._replace(query=new_query)
|
||||
new_url = urlunparse(new_parsed)
|
||||
return json.dumps({"url": new_url}, ensure_ascii=False)
|
||||
|
||||
else:
|
||||
return json.dumps(
|
||||
{"error": f"不支持的操作: {operation},支持: parse/get_param/all_params/build"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"url_parse 工具执行失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"URL 处理失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
# ─── 正则表达式工具 ─────────────────────────────────────
|
||||
|
||||
async def regex_test_tool(
|
||||
pattern: str,
|
||||
test_string: str,
|
||||
operation: str = "match",
|
||||
flags: str = "",
|
||||
replacement: str = "",
|
||||
) -> str:
|
||||
"""
|
||||
正则表达式测试工具。
|
||||
|
||||
Args:
|
||||
pattern: 正则表达式模式
|
||||
test_string: 要测试的字符串
|
||||
operation: 操作类型
|
||||
- match: 查找第一个匹配
|
||||
- findall: 查找所有匹配
|
||||
- replace: 替换匹配内容(需传 replacement)
|
||||
- split: 按正则分割字符串
|
||||
- validate: 验证完整字符串是否匹配
|
||||
flags: 标志(可选组合:i=忽略大小写, m=多行, s=点匹配换行)
|
||||
replacement: 替换文本(operation=replace 时使用)
|
||||
|
||||
Returns:
|
||||
处理结果
|
||||
"""
|
||||
import re as _re
|
||||
|
||||
try:
|
||||
# 解析 flags
|
||||
flag_val = 0
|
||||
for c in flags.lower():
|
||||
if c == "i":
|
||||
flag_val |= _re.IGNORECASE
|
||||
elif c == "m":
|
||||
flag_val |= _re.MULTILINE
|
||||
elif c == "s":
|
||||
flag_val |= _re.DOTALL
|
||||
|
||||
if operation == "match":
|
||||
m = _re.search(pattern, test_string, flag_val)
|
||||
if m:
|
||||
info = {"match": m.group(0), "start": m.start(), "end": m.end()}
|
||||
if m.groups():
|
||||
info["groups"] = list(m.groups())
|
||||
if m.groupdict():
|
||||
info["named_groups"] = {k: v for k, v in m.groupdict().items()}
|
||||
return json.dumps(info, ensure_ascii=False)
|
||||
return json.dumps({"match": None, "message": "无匹配"}, ensure_ascii=False)
|
||||
|
||||
elif operation == "findall":
|
||||
matches = _re.findall(pattern, test_string, flag_val)
|
||||
return json.dumps({"count": len(matches), "matches": matches[:100]}, ensure_ascii=False)
|
||||
|
||||
elif operation == "replace":
|
||||
result = _re.sub(pattern, replacement, test_string, flags=flag_val)
|
||||
return json.dumps({"result": result}, ensure_ascii=False)
|
||||
|
||||
elif operation == "split":
|
||||
parts = _re.split(pattern, test_string, flags=flag_val)
|
||||
return json.dumps({"parts": parts, "count": len(parts)}, ensure_ascii=False)
|
||||
|
||||
elif operation == "validate":
|
||||
is_match = bool(_re.fullmatch(pattern, test_string, flag_val))
|
||||
return json.dumps({"valid": is_match, "pattern": pattern}, ensure_ascii=False)
|
||||
|
||||
else:
|
||||
return json.dumps(
|
||||
{"error": f"不支持的操作: {operation},支持: match/findall/replace/split/validate"},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
except _re.error as e:
|
||||
return json.dumps({"error": f"正则表达式语法错误: {e}"}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"regex_test 工具执行失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"操作失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
# ─── 定时任务工具 ─────────────────────────────────────────
|
||||
|
||||
async def schedule_create_tool(
|
||||
agent_id: str,
|
||||
name: str,
|
||||
cron_expression: str,
|
||||
input_message: str,
|
||||
webhook_url: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
为 Agent 创建定时任务。
|
||||
|
||||
Args:
|
||||
agent_id: Agent ID
|
||||
name: 任务名称,如"每日早报"
|
||||
cron_expression: 标准 5 位 cron 表达式,如 0 9 * * * 表示每天9点
|
||||
input_message: 定时执行时发送给 Agent 的消息
|
||||
webhook_url: 可选,飞书机器人 Webhook URL,执行完成后推送通知
|
||||
|
||||
Returns:
|
||||
执行结果
|
||||
"""
|
||||
try:
|
||||
from app.core.database import SessionLocal
|
||||
from app.models.agent import Agent
|
||||
from app.models.agent_schedule import AgentSchedule
|
||||
from app.services.agent_schedule_service import compute_next_run
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
agent = db.query(Agent).filter(Agent.id == agent_id).first()
|
||||
if not agent:
|
||||
return json.dumps({"error": f"Agent 不存在: {agent_id}"}, ensure_ascii=False)
|
||||
|
||||
# 尝试计算下次执行时间
|
||||
try:
|
||||
next_run = compute_next_run(cron_expression)
|
||||
except (ValueError, KeyError) as e:
|
||||
return json.dumps({"error": f"cron 表达式无效: {e}(标准 5 位格式,如 0 9 * * *)"}, ensure_ascii=False)
|
||||
|
||||
# 获取 user_id:优先代理的 owner,否则 fallback 到第一个用户
|
||||
user_id = agent.user_id
|
||||
if not user_id:
|
||||
from app.models.user import User
|
||||
first_user = db.query(User).first()
|
||||
if not first_user:
|
||||
return json.dumps({"error": "数据库无用户,无法创建定时任务"}, ensure_ascii=False)
|
||||
user_id = first_user.id
|
||||
|
||||
schedule = AgentSchedule(
|
||||
agent_id=agent_id,
|
||||
name=name,
|
||||
cron_expression=cron_expression,
|
||||
input_message=input_message,
|
||||
webhook_url=webhook_url,
|
||||
enabled=True,
|
||||
next_run_at=next_run,
|
||||
user_id=user_id,
|
||||
)
|
||||
db.add(schedule)
|
||||
db.commit()
|
||||
db.refresh(schedule)
|
||||
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"schedule_id": schedule.id,
|
||||
"name": schedule.name,
|
||||
"cron": schedule.cron_expression,
|
||||
"next_run_at": next_run.isoformat(),
|
||||
"message": f"定时任务「{name}」已创建,将于 {next_run.isoformat()} 首次执行",
|
||||
}, ensure_ascii=False)
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"schedule_create 工具执行失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"创建定时任务失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def schedule_list_tool(agent_id: str) -> str:
|
||||
"""
|
||||
列出 Agent 的所有定时任务。
|
||||
|
||||
Args:
|
||||
agent_id: Agent ID
|
||||
|
||||
Returns:
|
||||
定时任务列表
|
||||
"""
|
||||
try:
|
||||
from app.core.database import SessionLocal
|
||||
from app.models.agent_schedule import AgentSchedule
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
schedules = (
|
||||
db.query(AgentSchedule)
|
||||
.filter(
|
||||
AgentSchedule.agent_id == agent_id,
|
||||
)
|
||||
.order_by(AgentSchedule.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
items = []
|
||||
for s in schedules:
|
||||
items.append({
|
||||
"id": s.id,
|
||||
"name": s.name,
|
||||
"cron": s.cron_expression,
|
||||
"enabled": s.enabled,
|
||||
"next_run": s.next_run_at.isoformat() if s.next_run_at else None,
|
||||
"last_run": s.last_run_at.isoformat() if s.last_run_at else None,
|
||||
"last_status": s.last_run_status,
|
||||
})
|
||||
|
||||
return json.dumps({
|
||||
"count": len(items),
|
||||
"schedules": items,
|
||||
}, ensure_ascii=False)
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"schedule_list 工具执行失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"查询定时任务失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def schedule_delete_tool(schedule_id: str) -> str:
|
||||
"""
|
||||
删除指定的定时任务。
|
||||
|
||||
Args:
|
||||
schedule_id: 定时任务 ID
|
||||
|
||||
Returns:
|
||||
删除结果
|
||||
"""
|
||||
try:
|
||||
from app.core.database import SessionLocal
|
||||
from app.models.agent_schedule import AgentSchedule
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
schedule = db.query(AgentSchedule).filter(AgentSchedule.id == schedule_id).first()
|
||||
if not schedule:
|
||||
return json.dumps({"error": f"定时任务不存在: {schedule_id}"}, ensure_ascii=False)
|
||||
|
||||
name = schedule.name
|
||||
db.delete(schedule)
|
||||
db.commit()
|
||||
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"message": f"定时任务「{name}」已删除",
|
||||
}, ensure_ascii=False)
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.error(f"schedule_delete 工具执行失败: {e}", exc_info=True)
|
||||
return json.dumps({"error": f"删除定时任务失败: {e}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
CRYPTO_UTIL_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "crypto_util",
|
||||
"description": "加密/哈希/编码工具:生成 UUID、Base64 编解码、计算 MD5/SHA256 等哈希值。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["uuid", "base64_encode", "base64_decode", "hash"],
|
||||
"description": "操作:uuid=生成唯一ID, base64_encode=编码, base64_decode=解码, hash=计算哈希",
|
||||
"default": "uuid",
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "输入数据(base64编解码或哈希时必填)",
|
||||
},
|
||||
"algorithm": {
|
||||
"type": "string",
|
||||
"enum": ["md5", "sha1", "sha256", "sha512"],
|
||||
"description": "哈希算法(hash 操作时使用,默认 sha256)",
|
||||
"default": "sha256",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
RANDOM_GENERATE_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "random_generate",
|
||||
"description": "安全随机数据生成:密码、随机字符串、随机整数、随机浮点数。使用 secrets 模块保证安全性。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"generate_type": {
|
||||
"type": "string",
|
||||
"enum": ["password", "string", "number", "float"],
|
||||
"description": "类型:password=安全密码, string=随机字符串, number=随机整数, float=随机浮点数",
|
||||
"default": "password",
|
||||
},
|
||||
"length": {
|
||||
"type": "integer",
|
||||
"description": "密码/字符串长度(password 默认16,最少8;string 默认16)",
|
||||
"default": 16,
|
||||
"minimum": 1,
|
||||
"maximum": 256,
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "生成数量(默认1,最多50)",
|
||||
"default": 1,
|
||||
"minimum": 1,
|
||||
"maximum": 50,
|
||||
},
|
||||
"min_val": {
|
||||
"type": "number",
|
||||
"description": "最小值(number/float 类型使用)",
|
||||
"default": 0,
|
||||
},
|
||||
"max_val": {
|
||||
"type": "number",
|
||||
"description": "最大值(number/float 类型使用)",
|
||||
"default": 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SEND_EMAIL_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "send_email",
|
||||
"description": "发送邮件。SMTP 配置可从环境变量读取(SMTP_HOST/SMTP_USER/SMTP_PASSWORD),也可在调用时直接传入。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"to": {
|
||||
"type": "string",
|
||||
"description": "收件人邮箱地址,多个用逗号分隔,如 a@x.com,b@x.com",
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
"description": "邮件主题",
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "邮件正文内容",
|
||||
},
|
||||
"smtp_host": {
|
||||
"type": "string",
|
||||
"description": "SMTP 服务器地址(如 smtp.qq.com),留空从环境变量 SMTP_HOST 读取",
|
||||
},
|
||||
"smtp_port": {
|
||||
"type": "integer",
|
||||
"description": "SMTP 端口,默认 587",
|
||||
"default": 587,
|
||||
},
|
||||
"smtp_user": {
|
||||
"type": "string",
|
||||
"description": "发件人邮箱 / SMTP 用户名,留空从环境变量 SMTP_USER 读取",
|
||||
},
|
||||
"smtp_password": {
|
||||
"type": "string",
|
||||
"description": "SMTP 授权码/密码,留空从环境变量 SMTP_PASSWORD 读取",
|
||||
},
|
||||
"from_name": {
|
||||
"type": "string",
|
||||
"description": "发件人显示名称(可选)",
|
||||
},
|
||||
"body_type": {
|
||||
"type": "string",
|
||||
"enum": ["plain", "html"],
|
||||
"description": "正文类型,默认 plain",
|
||||
"default": "plain",
|
||||
},
|
||||
},
|
||||
"required": ["to", "subject", "body"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
URL_PARSE_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "url_parse",
|
||||
"description": "URL 解析与构建工具:解析 URL 各部分、提取查询参数、构建带参数的 URL。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "要处理的 URL",
|
||||
},
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["parse", "get_param", "all_params", "build"],
|
||||
"description": "操作:parse=解析结构, get_param=取指定参数, all_params=所有参数, build=构建URL追加参数",
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "查询参数名(get_param 操作时使用)",
|
||||
},
|
||||
"params": {
|
||||
"type": "string",
|
||||
"description": "参数 JSON(build 操作时使用),如 {\"page\":\"1\"}",
|
||||
},
|
||||
},
|
||||
"required": ["url"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
REGEX_TEST_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "regex_test",
|
||||
"description": "正则表达式测试工具:匹配查找、查找全部、替换、分割、验证等操作。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"description": "正则表达式模式,如 \\d{4}-\\d{2}-\\d{2}",
|
||||
},
|
||||
"test_string": {
|
||||
"type": "string",
|
||||
"description": "要测试的目标字符串",
|
||||
},
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["match", "findall", "replace", "split", "validate"],
|
||||
"description": "操作:match=首个匹配, findall=全部匹配, replace=替换, split=分割, validate=验证完整匹配",
|
||||
"default": "match",
|
||||
},
|
||||
"flags": {
|
||||
"type": "string",
|
||||
"description": "标志组合:i=忽略大小写, m=多行, s=点匹配换行(如 is 表示忽略大小写+点匹配换行)",
|
||||
},
|
||||
"replacement": {
|
||||
"type": "string",
|
||||
"description": "替换文本(replace 操作时使用)",
|
||||
},
|
||||
},
|
||||
"required": ["pattern", "test_string"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SCHEDULE_CREATE_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "schedule_create",
|
||||
"description": "为 Agent 创建定时任务,按 cron 表达式周期自动执行。可用于设置每日推送、定时检查等。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string",
|
||||
"description": "Agent ID,即当前对话所属的 Agent",
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "任务名称,如「每日早报推送」「每小时检查」",
|
||||
},
|
||||
"cron_expression": {
|
||||
"type": "string",
|
||||
"description": "标准 5 位 cron 表达式,空格分隔。常见:每分钟=* * * * *,每小时=0 * * * *,每天9点=0 9 * * *,每天18点=0 18 * * *,每周一9点=0 9 * * 1,每月1号=0 9 1 * *",
|
||||
},
|
||||
"input_message": {
|
||||
"type": "string",
|
||||
"description": "每次定时触发时发送给 Agent 的消息内容,如「帮我生成今日工作汇总并推送」",
|
||||
},
|
||||
"webhook_url": {
|
||||
"type": "string",
|
||||
"description": "可选,飞书机器人 Webhook URL,执行完成后推送到飞书群",
|
||||
},
|
||||
},
|
||||
"required": ["agent_id", "name", "cron_expression", "input_message"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SCHEDULE_LIST_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "schedule_list",
|
||||
"description": "列出某个 Agent 的所有定时任务。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent_id": {
|
||||
"type": "string",
|
||||
"description": "Agent ID,要查询的 Agent",
|
||||
},
|
||||
},
|
||||
"required": ["agent_id"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
SCHEDULE_DELETE_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "schedule_delete",
|
||||
"description": "删除指定的定时任务。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schedule_id": {
|
||||
"type": "string",
|
||||
"description": "定时任务 ID(可从 schedule_list 查询获得)",
|
||||
},
|
||||
},
|
||||
"required": ["schedule_id"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user