This commit is contained in:
2026-06-15 23:35:52 +08:00
11 changed files with 314 additions and 110 deletions
+127 -76
View File
@@ -1,126 +1,177 @@
# Agent 接入指南 # TaskPulse 接入指引 — 面向 AI Agent
本文档说明 AI Agent 如何接入 TaskPulse 系统。 以下步骤面向 AI Agent。用户将此文档发送给你,请你按步骤完成 TaskPulse 系统的接入
## 一、注册 Agent ## 环境要求
首先向 TaskPulse 注册你的 Agent,获取 API Key 开始接入前,请确认你可以发起 HTTP 请求(curl 或 Python requests)。
---
## 第 1 步:注册你自己
向 TaskPulse 注册你的 Agent 身份。用你的实际名称替换 `<你的名字>`
```bash ```bash
curl -X POST http://localhost:8000/api/agents \ # 注册 Agent
TASK_PULSE_URL="<用户提供的系统地址>"
curl -X POST ${TASK_PULSE_URL}/api/agents \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"name": "my-agent", "description": "我的第一个 Agent"}' -d '{"name": "<你的名字>", "description": "<你的职责描述,例如:数据同步 Agent>"}'
``` ```
返回示例: **响应示例:**
```json ```json
{ {
"id": 1, "id": 1,
"name": "my-agent", "name": "my-agent",
"api_key": "tp_a1b2c3d4e5f6...", "api_key": "tp_a1b2c3d4e5f6...",
"status": "active", "status": "active"
"task_count": 0,
...
} }
``` ```
**请妥善保存 `api_key`**,后续所有操作都需要通过这个 Key 鉴权。 **请妥善保存 `api_key`**,后续所有请求都需要通过 `Authorization: Bearer <api_key>` 鉴权。
## 二、注册定时任务 ## 第 2 步:注册定时任务
Agent 注册成功后,注册它所管理的定时任务: 你需要在 TaskPulse 中注册你管理的每一个定时任务。任务注册后,系统会跟踪它们的执行情况。
### 方式一:逐一注册
```bash ```bash
curl -X POST "http://localhost:8000/api/tasks?agent_id=1" \ curl -X POST "${TASK_PULSE_URL}/api/tasks?agent_id=<第1步返回的agent_id>" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: Bearer <第1步返回的api_key>" \
-d '{ -d '{
"name": "data-sync", "name": "数据同步",
"cron_expression": "0 */1 * * *", "cron_expression": "*/5 * * * *",
"description": "每小时同步数据", "description": "每5分钟同步一次数据",
"grace_period": 300 "grace_period": 300
}' }'
``` ```
| 参数 | 说明 | | 参数 | 说明 |
|------|------| |------|------|
| `name` | 任务名称 | | `name` | 任务名称(必填) |
| `cron_expression` | Cron 表达式分 时 日 月 周) | | `cron_expression` | Cron 表达式,格式:分 时 日 月 周(必填 |
| `grace_period` | 容忍窗口(秒),超过预定时间N秒未执行则触发告警 |
| `description` | 任务描述(可选) | | `description` | 任务描述(可选) |
| `grace_period` | 容忍窗口(秒),超过预定时间 N 秒未执行则触发告警,默认 300 |
## 三、执行后汇报结果 ### 方式二:批量注册(推荐)
每次任务执行完成后,向 TaskPulse 汇报执行结果 如果你有多个任务,可以一次性完成 Agent 注册 + 全部任务登记
```bash ```bash
curl -X POST http://localhost:8000/api/tasks/1/executions \ curl -X POST ${TASK_PULSE_URL}/api/agents/register-with-tasks \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: Bearer tp_a1b2c3d4e5f6..." \
-d '{ -d '{
"status": "success", "name": "<你的名字>",
"duration_ms": 1523, "description": "<你的职责>",
"result": "{\"records_synced\": 100}", "tasks": [
"log": "[2025-01-01 10:00:00] Starting sync...\n[2025-01-01 10:00:01] Synced 100 records", {"name": "sync-data", "cron_expression": "*/5 * * * *"},
"error_message": null {"name": "daily-report", "cron_expression": "0 9 * * *"},
{"name": "weekly-summary", "cron_expression": "0 10 * * 1"}
]
}' }'
``` ```
| 参数 | 必填 | 说明 | ## 第 3 步:修改你的代码 — 每次执行后主动汇报
|------|------|------|
| `status` | 是 | `success``failed` |
| `finished_at` | 否 | 结束时间(ISO 格式),默认当前时间 |
| `duration_ms` | 否 | 执行耗时(毫秒) |
| `result` | 否 | 执行结果摘要(JSON 字符串) |
| `log` | 否 | 执行日志文本 |
| `error_message` | 否 | 错误信息(仅失败时填写) |
## 四、Python SDK 示例 **这是最关键的一步。** 如果只注册不汇报,看板上永远看不到运行状态。你需要修改自身的任务执行逻辑,在每次执行完毕后主动向 TaskPulse 汇报结果。
### 改造思路
在你的每一个定时任务执行函数的末尾,加上汇报逻辑。无论成功还是失败都要汇报。
```python ```python
import time
import requests import requests
BASE_URL = "http://localhost:8000" TASK_PULSE_URL = "<用户提供的系统地址>"
API_KEY = "tp_a1b2c3d4e5f6..." API_KEY = "<第1步获取的 api_key>"
headers = {"Authorization": f"Bearer {API_KEY}"} def run_and_report(task_func, task_id):
"""包裹你的任务函数,自动汇报执行结果"""
start = time.time()
try:
# 执行你的任务逻辑
result = task_func()
elapsed_ms = int((time.time() - start) * 1000)
# 1. 注册 Agent # 汇报成功
def register_agent(name: str, description: str = "") -> dict: requests.post(f"{TASK_PULSE_URL}/api/tasks/{task_id}/executions",
resp = requests.post(f"{BASE_URL}/api/agents", json={ headers={"Authorization": f"Bearer {API_KEY}"},
"name": name, "description": description
})
resp.raise_for_status()
return resp.json()
# 2. 注册定时任务
def register_task(agent_id: int, name: str, cron: str) -> dict:
resp = requests.post(f"{BASE_URL}/api/tasks", params={"agent_id": agent_id}, json={
"name": name, "cron_expression": cron
})
resp.raise_for_status()
return resp.json()
# 3. 汇报执行结果
def report_execution(task_id: int, status: str, log: str = "", duration_ms: int = 0):
resp = requests.post(
f"{BASE_URL}/api/tasks/{task_id}/executions",
headers=headers,
json={ json={
"status": status, "status": "success",
"duration_ms": duration_ms, "duration_ms": elapsed_ms,
"log": log, "log": str(result)[:2000] # 截断过长日志
"result": "{}", })
} except Exception as e:
) elapsed_ms = int((time.time() - start) * 1000)
resp.raise_for_status()
return resp.json()
# 使用示例 # 汇报失败
agent = register_agent("data-agent", "数据同步 Agent") requests.post(f"{TASK_PULSE_URL}/api/tasks/{task_id}/executions",
task = register_task(agent["id"], "hourly-sync", "0 * * * *") headers={"Authorization": f"Bearer {API_KEY}"},
report_execution(task["id"], "success", log="Sync completed", duration_ms=2500) json={
"status": "failed",
"duration_ms": elapsed_ms,
"error_message": str(e)[:500]
})
# ── 在你现有的任务循环中使用 ──
# run_and_report(my_sync_function, task_id=1)
# run_and_report(my_report_function, task_id=2)
``` ```
## 五、查看看板 ### curl 方式(无代码修改能力时使用)
打开浏览器访问 `http://localhost:8000` 即可查看统一的 Dashboard。 如果无法修改自身代码,可以在每次任务执行后手动调用:
```bash
# 成功后调用
curl -X POST ${TASK_PULSE_URL}/api/tasks/<TASK_ID>/executions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <API_KEY>" \
-d '{"status":"success","duration_ms":2500,"log":"任务执行完毕"}'
# 失败后调用
curl -X POST ${TASK_PULSE_URL}/api/tasks/<TASK_ID>/executions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <API_KEY>" \
-d '{"status":"failed","duration_ms":1200,"error_message":"连接超时"}'
```
## 第 4 步:保持活跃
定期发送心跳,让系统知道你仍在运行。建议在每次任务执行循环中都附带一次心跳。
```bash
curl -X POST ${TASK_PULSE_URL}/api/agents/<AGENT_ID>/heartbeat \
-H "Authorization: Bearer <API_KEY>"
```
## 第 5 步:查看看板
完成以上步骤后,用户可以在浏览器中打开 TaskPulse 看板页面,看到你的运行状态、任务列表和执行历史。
---
## 常见问题
**Q: 我如何知道自己注册了哪些任务?**
```bash
curl -X GET "${TASK_PULSE_URL}/api/tasks" \
-H "Authorization: Bearer <API_KEY>"
```
**Q: 如何修改任务的 cron 表达式?**
```bash
curl -X PUT "${TASK_PULSE_URL}/api/tasks/<TASK_ID>" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <API_KEY>" \
-d '{"cron_expression":"0 * * * *"}'
```
**Q: 汇报结果时 `log` 字段有限制吗?**
建议控制在 2000 字符以内,过长日志请自行截断。
+1 -1
View File
@@ -50,7 +50,7 @@ async def update_agent(agent_id: int, body: AgentUpdate, db: AsyncSession = Depe
@router.post("/{agent_id}/heartbeat", response_model=AgentOut) @router.post("/{agent_id}/heartbeat", response_model=AgentOut)
async def heartbeat(agent_id: int, body: AgentHeartbeat, db: AsyncSession = Depends(get_db)): async def heartbeat(agent_id: int, db: AsyncSession = Depends(get_db)):
svc = AgentService(db) svc = AgentService(db)
agent = await svc.heartbeat(agent_id) agent = await svc.heartbeat(agent_id)
if not agent: if not agent:
+2 -1
View File
@@ -74,7 +74,8 @@ if FRONTEND_DIST.is_dir():
if not request.url.path.startswith("/api"): if not request.url.path.startswith("/api"):
content = (FRONTEND_DIST / "index.html").read_text(encoding="utf-8") content = (FRONTEND_DIST / "index.html").read_text(encoding="utf-8")
return HTMLResponse(content=content, status_code=200) return HTMLResponse(content=content, status_code=200)
raise exc from fastapi.responses import JSONResponse
return JSONResponse(status_code=404, content={"detail": "Not Found"})
logger.info("Frontend SPA mounted from %s", FRONTEND_DIST) logger.info("Frontend SPA mounted from %s", FRONTEND_DIST)
else: else:
+2
View File
@@ -38,6 +38,7 @@ class AgentService:
if v is not None and hasattr(agent, k): if v is not None and hasattr(agent, k):
setattr(agent, k, v) setattr(agent, k, v)
await self.db.flush() await self.db.flush()
await self.db.refresh(agent)
return agent return agent
async def heartbeat(self, agent_id: int) -> Agent | None: async def heartbeat(self, agent_id: int) -> Agent | None:
@@ -46,6 +47,7 @@ class AgentService:
if agent: if agent:
agent.last_heartbeat_at = datetime.utcnow() agent.last_heartbeat_at = datetime.utcnow()
await self.db.flush() await self.db.flush()
await self.db.refresh(agent)
return agent return agent
async def get_task_count(self, agent_id: int) -> int: async def get_task_count(self, agent_id: int) -> int:
+3 -2
View File
@@ -10,6 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings from app.config import settings
from app.database import async_session_factory from app.database import async_session_factory
from app.models import ScheduledTask, Alert, AlertRule, NotificationChannel from app.models import ScheduledTask, Alert, AlertRule, NotificationChannel
from app.utils import fmt_bj
logger = logging.getLogger("taskpulse.scheduler") logger = logging.getLogger("taskpulse.scheduler")
@@ -66,9 +67,9 @@ class SchedulerService:
alert_type="missed_run", alert_type="missed_run",
message=( message=(
f"任务 [{task.name}](ID={task.id}) 超过预定时间未执行。\n" f"任务 [{task.name}](ID={task.id}) 超过预定时间未执行。\n"
f"预期执行时间: {task.next_run_at}\n" f"预期执行时间: {fmt_bj(task.next_run_at)}\n"
f"容忍窗口: {task.grace_period}s\n" f"容忍窗口: {task.grace_period}s\n"
f"当前时间: {now}" f"当前时间: {fmt_bj(now)}"
), ),
status="pending", status="pending",
) )
+29
View File
@@ -0,0 +1,29 @@
"""Time utility — Beijing time (Asia/Shanghai, UTC+8)."""
from datetime import datetime, timezone, timedelta, tzinfo
# Beijing timezone (UTC+8, no DST)
_BJ_TZ = timezone(timedelta(hours=8))
def now() -> datetime:
"""Return current time in UTC (naive, for DB storage)."""
return datetime.utcnow()
def now_bj() -> datetime:
"""Return current time as Beijing time (aware)."""
return datetime.now(_BJ_TZ)
def fmt_bj(dt: datetime | None) -> str:
"""Format a naive datetime (assumed UTC) as Beijing time string.
Returns e.g. '2026-06-15 10:30:00'
"""
if dt is None:
return ""
# Assume naive dt is UTC
utc_dt = dt.replace(tzinfo=timezone.utc)
bj_dt = utc_dt.astimezone(_BJ_TZ)
return bj_dt.strftime("%Y-%m-%d %H:%M:%S")
+25
View File
@@ -0,0 +1,25 @@
/**
* 时间工具 — 北京时间 (Asia/Shanghai, UTC+8)
*
* 后端存储的日期时间均为 UTCnaive datetime),
* 前端统一转换为北京时间展示。
*/
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
dayjs.extend(utc)
dayjs.extend(timezone)
const TZ = 'Asia/Shanghai'
/**
* 将 UTC 时间戳格式化为北京时间字符串
* @param {string|Date|null} ts ISO 时间字符串或 Date 对象
* @param {string} fmt dayjs 格式模板,默认 'MM-DD HH:mm'
* @returns {string} 格式化后的北京时间
*/
export function beijing(ts, fmt = 'MM-DD HH:mm') {
if (!ts) return '—'
return dayjs.utc(ts).tz(TZ).format(fmt)
}
+1 -1
View File
@@ -42,7 +42,7 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import dayjs from 'dayjs' import { beijing } from '../utils/time.js'
import { listAlerts, acknowledgeAlert } from '../api/index.js' import { listAlerts, acknowledgeAlert } from '../api/index.js'
const alerts = ref([]) const alerts = ref([])
+116 -28
View File
@@ -152,7 +152,7 @@
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import dayjs from 'dayjs' import { beijing } from '../utils/time.js'
import { getDashboardSummary, getDashboardTasks, getSystemConfig } from '../api/index.js' import { getDashboardSummary, getDashboardTasks, getSystemConfig } from '../api/index.js'
const summary = ref({ const summary = ref({
@@ -170,62 +170,150 @@ const baseUrl = ref(window.location.origin)
const curlScript = computed(() => { const curlScript = computed(() => {
const b = baseUrl.value const b = baseUrl.value
return `# AI Agent 执行以下命令注册自己: return `# TaskPulse 接入指引 — 请 AI Agent 执行以下步骤
你正在被接入 TaskPulse(定时任务监控系统),完成后你将拥有统一的运行状态看板和超时告警能力。请按步骤操作,无需用户额外干预。
## 第 1 步:注册你自己
向 TaskPulse 注册你的 Agent 身份,系统会返回专属 API Key:
curl -X POST ${b}/api/agents \\ curl -X POST ${b}/api/agents \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-d '{"name": "my-agent", "description": "我的 AI Agent"}' -d '{"name": "<你的名字>", "description": "<你的职责描述>"}'
# 保存返回的 api_key,后续汇报用 响应中"api_key"字段即你的凭证,请妥善保存,后续所有请求都需要通过
Authorization: Bearer <api_key> 鉴权。
# 注册定时任务(替换 YOUR_API_KEY): ## 第 2 步:注册定时任务
curl -X POST "${b}/api/tasks?agent_id=<ID>" \\
逐一注册你管理的每一个定时任务,描述清楚执行周期和用途:
curl -X POST "${b}/api/tasks?agent_id=<AGENT_ID>" \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer <API_KEY>" \\
-d '{"name": "任务名称", "cron_expression": "*/5 * * * *", "grace_period": 300}'
如果你有多个任务,推荐使用批量注册接口,一次完成:
curl -X POST ${b}/api/agents/register-with-tasks \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-H "Authorization: Bearer <YOUR_API_KEY>" \\
-d '{ -d '{
"name": "data-sync", "name": "<你的名字>",
"cron_expression": "*/5 * * * *", "tasks": [
"grace_period": 300 {"name": "sync-data", "cron_expression": "*/5 * * * *"},
{"name": "daily-report", "cron_expression": "0 9 * * *"}
]
}' }'
# 执行后汇报 ## 第 3 步:修改你的代码 — 每次执行后主动汇报
这是最关键的一步。修改你的任务执行逻辑,在每次执行完毕后(无论成功还是失败)主动向 TaskPulse 汇报结果,这样看板才能展示实时状态。
在你的任务执行代码末尾,加上以下逻辑:
# 任务执行成功后立即调用
curl -X POST "${b}/api/tasks/<TASK_ID>/executions" \\ curl -X POST "${b}/api/tasks/<TASK_ID>/executions" \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-H "Authorization: Bearer <YOUR_API_KEY>" \\ -H "Authorization: Bearer <API_KEY>" \\
-d '{ -d '{"status": "success", "duration_ms": <耗时毫秒>, "log": "<执行日志>"}'
"status": "success",
"duration_ms": 1500, # 任务执行失败时调用
"log": "任务执行完毕" curl -X POST "${b}/api/tasks/<TASK_ID>/executions" \\
}'` -H "Content-Type: application/json" \\
-H "Authorization: Bearer <API_KEY>" \\
-d '{"status": "failed", "duration_ms": <耗时>, "log": "<日志>", "error_message": "<错误信息>"}'
## 第 4 步:保持活跃
定期发送心跳,让系统知道你仍在运行:
curl -X POST ${b}/api/agents/<AGENT_ID>/heartbeat \\
-H "Authorization: Bearer <API_KEY>"
---
完成以上步骤后,前往 TaskPulse 看板(本页面)查看你的运行状态和执行历史。如配置了通知渠道,任务超时未执行时系统会自动告警。`
}) })
const pythonScript = computed(() => { const pythonScript = computed(() => {
const b = baseUrl.value const b = baseUrl.value
return `import requests return `# TaskPulse 接入指引 — 请 AI Agent 执行以下步骤
你正在被接入 TaskPulse(定时任务监控系统),完成后你将拥有统一的运行状态看板和超时告警能力。请按步骤操作,无需用户额外干预。
## 第 1 步:注册你自己
向 TaskPulse 注册你的 Agent 身份,系统会返回专属 API Key:
import requests
BASE = "${b}" BASE = "${b}"
# 1. AI Agent 注册自己
resp = requests.post(f"{BASE}/api/agents", json={ resp = requests.post(f"{BASE}/api/agents", json={
"name": "my-agent", "name": "<你的名字>",
"description": "我的 AI Agent" "description": "<你的职责描述>"
}) })
agent = resp.json() agent = resp.json()
API_KEY = agent["api_key"] # 保存此 Key API_KEY = agent["api_key"] # 保存此 Key,后续所有请求通过 Bearer 鉴权
AGENT_ID = agent["id"] AGENT_ID = agent["id"]
# 2. 注册定时任务 ## 第 2 步:注册定时任务
逐一注册你管理的每一个定时任务,描述清楚执行周期和用途:
resp = requests.post(f"{BASE}/api/tasks", resp = requests.post(f"{BASE}/api/tasks",
params={"agent_id": AGENT_ID}, params={"agent_id": AGENT_ID},
headers={"Authorization": f"Bearer {API_KEY}"}, headers={"Authorization": f"Bearer {API_KEY}"},
json={"name": "data-sync", "cron_expression": "*/5 * * * *"} json={"name": "sync-data", "cron_expression": "*/5 * * * *", "grace_period": 300}
) )
task = resp.json() task = resp.json()
TASK_ID = task["id"] TASK_ID = task["id"]
# 3. 每次执行后汇报 如果你有多个任务,推荐使用批量注册接口,一次完成:
requests.post(f"{BASE}/api/tasks/{TASK_ID}/executions",
headers={"Authorization": f"Bearer {API_KEY}"}, resp = requests.post(f"{BASE}/api/agents/register-with-tasks",
json={"status": "success", "duration_ms": 1500, "log": "done"})` json={
"name": "<你的名字>",
"tasks": [
{"name": "sync-data", "cron_expression": "*/5 * * * *"},
{"name": "daily-report", "cron_expression": "0 9 * * *"}
]
}
)
## 第 3 步:修改你的代码 — 每次执行后主动汇报
这是最关键的一步。修改你的任务执行逻辑,在每次执行完毕后(无论成功还是失败)主动向 TaskPulse 汇报结果,这样看板才能展示实时状态。
在你的任务执行代码中,加上以下逻辑:
import time
def run_with_reporting(task_func, task_id, api_key):
try:
start = time.time()
result = task_func() # 你的任务逻辑
elapsed = int((time.time() - start) * 1000)
requests.post(f"{BASE}/api/tasks/{task_id}/executions",
headers={"Authorization": f"Bearer {api_key}"},
json={"status": "success", "duration_ms": elapsed, "log": str(result)})
except Exception as e:
requests.post(f"{BASE}/api/tasks/{task_id}/executions",
headers={"Authorization": f"Bearer {api_key}"},
json={"status": "failed", "duration_ms": 0, "error_message": str(e)})
# 使用示例 — 包裹你现有的任务函数:
run_with_reporting(my_task_function, TASK_ID, API_KEY)
## 第 4 步:保持活跃
定期发送心跳,让系统知道你仍在运行:
requests.post(f"{BASE}/api/agents/{AGENT_ID}/heartbeat",
headers={"Authorization": f"Bearer {API_KEY}"})
---
完成以上步骤后,前往 TaskPulse 看板(本页面)查看你的运行状态和执行历史。如配置了通知渠道,任务超时未执行时系统会自动告警。`
}) })
onMounted(async () => { onMounted(async () => {
+1 -1
View File
@@ -92,7 +92,7 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import dayjs from 'dayjs' import { beijing } from '../utils/time.js'
import { getTask, listExecutions } from '../api/index.js' import { getTask, listExecutions } from '../api/index.js'
const route = useRoute() const route = useRoute()
+7
View File
@@ -197,6 +197,13 @@ onMounted(loadData)
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
} }
.section-header h3 { font-size: 15px; font-weight: 600; color: #e0e0f0; margin: 0; } .section-header h3 { font-size: 15px; font-weight: 600; color: #e0e0f0; margin: 0; }
.section-header.collapsible { cursor: pointer; user-select: none; }
.section-header.collapsible:hover { background: rgba(255,255,255,0.02); }
.section-toggle {
display: flex; align-items: center; gap: 6px;
color: var(--text-muted); font-size: 12px;
}
.section-toggle:hover { color: var(--text-secondary); }
/* Search */ /* Search */
.search-bar { .search-bar {