升级到 PostgreSQL 并添加 Docker Compose 部署
主要更新: - 替换 SQLite 为 PostgreSQL 数据库 - 添加 Docker Compose 配置文件 - 添加 Dockerfile 支持容器化部署 - 更新 README 添加详细的智能体注册指南 - 添加 .env.example 环境变量模板 - 修复 uuid 模块兼容性问题(降级到 9.x) - 完善 API 文档和使用示例
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# 数据库配置
|
||||||
|
DB_HOST=postgres
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=agent_tasks
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=postgres
|
||||||
|
|
||||||
|
# 应用配置
|
||||||
|
PORT=3000
|
||||||
@@ -3,3 +3,4 @@ node_modules/
|
|||||||
*.log
|
*.log
|
||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
postgres_data/
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制 package 文件
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# 复制应用代码
|
||||||
|
COPY server-postgres.js ./server.js
|
||||||
|
COPY public ./public
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -10,21 +10,163 @@
|
|||||||
- ✅ **状态实时更新** - 跟踪任务和智能体的状态变化
|
- ✅ **状态实时更新** - 跟踪任务和智能体的状态变化
|
||||||
- ✅ **Web 管理界面** - 可视化查看和管理任务
|
- ✅ **Web 管理界面** - 可视化查看和管理任务
|
||||||
- ✅ **RESTful API** - 完整的 HTTP API 接口
|
- ✅ **RESTful API** - 完整的 HTTP API 接口
|
||||||
|
- ✅ **PostgreSQL 存储** - 稳定可靠的关系型数据库
|
||||||
|
- ✅ **Docker 部署** - 一键启动完整环境
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始(Docker Compose)
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
- Docker
|
||||||
|
- Docker Compose
|
||||||
|
|
||||||
### 1. 启动服务
|
### 1. 启动服务
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /data/openclaw/workspace/agent-task-service
|
cd /data/openclaw/workspace/agent-task-service
|
||||||
node server.js
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
服务将在 `http://localhost:3000` 启动。
|
服务将在以下地址启动:
|
||||||
|
- **Web 界面**: http://localhost:3000
|
||||||
|
- **API 接口**: http://localhost:3000/api
|
||||||
|
- **PostgreSQL**: localhost:5432
|
||||||
|
|
||||||
### 2. 访问 Web 界面
|
### 2. 查看日志
|
||||||
|
|
||||||
在浏览器中打开: `http://localhost:3000`
|
```bash
|
||||||
|
docker-compose logs -f app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 停止服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 完全清理(包括数据)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose down -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## 本地开发
|
||||||
|
|
||||||
|
### 前置要求
|
||||||
|
- Node.js 18+
|
||||||
|
- PostgreSQL 12+
|
||||||
|
|
||||||
|
### 1. 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置数据库
|
||||||
|
|
||||||
|
复制环境变量模板:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
编辑 `.env` 文件,配置数据库连接:
|
||||||
|
```env
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=agent_tasks
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=your_password
|
||||||
|
PORT=3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 创建数据库
|
||||||
|
|
||||||
|
```bash
|
||||||
|
psql -U postgres -c "CREATE DATABASE agent_tasks;"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node server-postgres.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## 智能体注册与使用指南
|
||||||
|
|
||||||
|
### 步骤 1: 注册智能体
|
||||||
|
|
||||||
|
智能体首次启动时需要向系统注册:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3000/api/agents/register \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"name": "我的智能体",
|
||||||
|
"type": "general",
|
||||||
|
"capabilities": ["数据分析", "文本处理", "API调用"]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"name": "我的智能体",
|
||||||
|
"type": "general",
|
||||||
|
"status": "idle",
|
||||||
|
"message": "智能体注册成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**重要提示:**
|
||||||
|
- 保存返回的 `id`,后续所有操作都需要这个 ID
|
||||||
|
- `name`: 智能体的显示名称
|
||||||
|
- `type`: 类型(可选)如 `general`, `search`, `coding`, `analysis` 等
|
||||||
|
- `capabilities`: 能力列表(可选)用于说明智能体可以做什么
|
||||||
|
|
||||||
|
### 步骤 2: 维持心跳
|
||||||
|
|
||||||
|
智能体需要定期发送心跳(建议每 30-60 秒):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3000/api/agents/{agent_id}/heartbeat \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"status": "idle"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
状态值:
|
||||||
|
- `idle`: 空闲,可以接受新任务
|
||||||
|
- `busy`: 忙碌中,正在执行任务
|
||||||
|
|
||||||
|
### 步骤 3: 查询待处理任务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/tasks?status=pending
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 4: 领取任务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3000/api/tasks/{task_id}/claim \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"agent_id": "your-agent-id"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 5: 更新任务状态
|
||||||
|
|
||||||
|
执行完任务后,更新状态为完成:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X PUT http://localhost:3000/api/tasks/{task_id} \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"status": "completed",
|
||||||
|
"result": "任务执行结果描述"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
## API 文档
|
## API 文档
|
||||||
|
|
||||||
@@ -36,9 +178,9 @@ POST /api/agents/register
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "智能体名称",
|
"name": "智能体名称", // 必填
|
||||||
"type": "general", // 可选: general, search, coding, etc.
|
"type": "general", // 可选: general, search, coding, etc.
|
||||||
"capabilities": ["搜索", "编程", "分析"] // 可选
|
"capabilities": ["搜索", "编程"] // 可选: 能力列表
|
||||||
}
|
}
|
||||||
|
|
||||||
响应:
|
响应:
|
||||||
@@ -87,10 +229,10 @@ POST /api/tasks
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"title": "任务标题",
|
"title": "任务标题", // 必填
|
||||||
"description": "任务详细描述",
|
"description": "任务详细描述", // 可选
|
||||||
"priority": "normal", // low | normal | high
|
"priority": "normal", // 可选: low | normal | high
|
||||||
"created_by": "human" // human | agent_id
|
"created_by": "human" // 可选: human | agent_id
|
||||||
}
|
}
|
||||||
|
|
||||||
响应:
|
响应:
|
||||||
@@ -129,7 +271,7 @@ POST /api/tasks/:id/claim
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"agent_id": "智能体ID"
|
"agent_id": "智能体ID" // 必填
|
||||||
}
|
}
|
||||||
|
|
||||||
响应:
|
响应:
|
||||||
@@ -144,7 +286,7 @@ PUT /api/tasks/:id
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"status": "completed", // pending | in_progress | completed
|
"status": "completed", // 可选: pending | in_progress | completed
|
||||||
"result": "任务执行结果" // 可选
|
"result": "任务执行结果" // 可选
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,14 +331,24 @@ GET /api/stats
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 健康检查
|
||||||
|
```http
|
||||||
|
GET /health
|
||||||
|
|
||||||
|
响应:
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"database": "connected"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 智能体集成示例
|
## 智能体集成示例
|
||||||
|
|
||||||
### Python 客户端示例
|
### Python 客户端完整示例
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
import uuid
|
|
||||||
|
|
||||||
class TaskAgent:
|
class TaskAgent:
|
||||||
def __init__(self, name, base_url="http://localhost:3000"):
|
def __init__(self, name, base_url="http://localhost:3000"):
|
||||||
@@ -206,51 +358,93 @@ class TaskAgent:
|
|||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
"""注册智能体"""
|
"""注册智能体"""
|
||||||
|
try:
|
||||||
response = requests.post(f"{self.base_url}/api/agents/register", json={
|
response = requests.post(f"{self.base_url}/api/agents/register", json={
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"type": "general",
|
"type": "general",
|
||||||
"capabilities": ["general_task"]
|
"capabilities": ["general_task", "data_processing"]
|
||||||
})
|
})
|
||||||
|
response.raise_for_status()
|
||||||
data = response.json()
|
data = response.json()
|
||||||
self.agent_id = data["id"]
|
self.agent_id = data["id"]
|
||||||
print(f"✅ 智能体已注册: {self.agent_id}")
|
print(f"✅ 智能体已注册: {self.agent_id}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 注册失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def heartbeat(self, status="idle"):
|
def heartbeat(self, status="idle"):
|
||||||
"""发送心跳"""
|
"""发送心跳"""
|
||||||
|
try:
|
||||||
requests.post(
|
requests.post(
|
||||||
f"{self.base_url}/api/agents/{self.agent_id}/heartbeat",
|
f"{self.base_url}/api/agents/{self.agent_id}/heartbeat",
|
||||||
json={"status": status}
|
json={"status": status},
|
||||||
|
timeout=5
|
||||||
)
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 心跳发送失败: {e}")
|
||||||
|
|
||||||
def get_pending_tasks(self):
|
def get_pending_tasks(self):
|
||||||
"""获取待处理任务"""
|
"""获取待处理任务"""
|
||||||
response = requests.get(f"{self.base_url}/api/tasks?status=pending")
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
f"{self.base_url}/api/tasks?status=pending",
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 获取任务失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
def claim_task(self, task_id):
|
def claim_task(self, task_id):
|
||||||
"""领取任务"""
|
"""领取任务"""
|
||||||
|
try:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.base_url}/api/tasks/{task_id}/claim",
|
f"{self.base_url}/api/tasks/{task_id}/claim",
|
||||||
json={"agent_id": self.agent_id}
|
json={"agent_id": self.agent_id},
|
||||||
|
timeout=5
|
||||||
)
|
)
|
||||||
return response.ok
|
response.raise_for_status()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ 领取任务失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def update_task(self, task_id, status, result=None):
|
def update_task(self, task_id, status, result=None):
|
||||||
"""更新任务状态"""
|
"""更新任务状态"""
|
||||||
|
try:
|
||||||
payload = {"status": status}
|
payload = {"status": status}
|
||||||
if result:
|
if result:
|
||||||
payload["result"] = result
|
payload["result"] = result
|
||||||
requests.put(f"{self.base_url}/api/tasks/{task_id}", json=payload)
|
response = requests.put(
|
||||||
|
f"{self.base_url}/api/tasks/{task_id}",
|
||||||
|
json=payload,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 更新任务失败: {e}")
|
||||||
|
|
||||||
def execute_task(self, task):
|
def execute_task(self, task):
|
||||||
"""执行任务(示例)"""
|
"""执行任务(示例实现)"""
|
||||||
print(f"🔄 正在执行任务: {task['title']}")
|
print(f"🔄 正在执行任务: {task['title']}")
|
||||||
time.sleep(2) # 模拟任务执行
|
print(f" 描述: {task.get('description', '无')}")
|
||||||
return f"任务 '{task['title']}' 已完成"
|
print(f" 优先级: {task['priority']}")
|
||||||
|
|
||||||
|
# 模拟任务执行
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
return f"任务 '{task['title']}' 已由 {self.name} 成功完成"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""主循环"""
|
"""主循环"""
|
||||||
self.register()
|
if not self.register():
|
||||||
|
print("❌ 无法注册智能体,退出")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"🤖 智能体 '{self.name}' 开始运行...")
|
||||||
|
print("按 Ctrl+C 退出\n")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -262,10 +456,11 @@ class TaskAgent:
|
|||||||
|
|
||||||
if tasks:
|
if tasks:
|
||||||
task = tasks[0]
|
task = tasks[0]
|
||||||
print(f"📋 发现任务: {task['title']}")
|
print(f"\n📋 发现新任务: {task['title']}")
|
||||||
|
|
||||||
# 领取任务
|
# 领取任务
|
||||||
if self.claim_task(task['id']):
|
if self.claim_task(task['id']):
|
||||||
|
print("✓ 任务已领取")
|
||||||
self.heartbeat("busy")
|
self.heartbeat("busy")
|
||||||
|
|
||||||
# 执行任务
|
# 执行任务
|
||||||
@@ -273,22 +468,23 @@ class TaskAgent:
|
|||||||
|
|
||||||
# 更新任务状态
|
# 更新任务状态
|
||||||
self.update_task(task['id'], "completed", result)
|
self.update_task(task['id'], "completed", result)
|
||||||
print(f"✅ 任务完成: {task['id']}")
|
print(f"✅ 任务完成\n")
|
||||||
|
|
||||||
self.heartbeat("idle")
|
self.heartbeat("idle")
|
||||||
|
|
||||||
time.sleep(5) # 每5秒检查一次
|
# 等待一段时间再检查
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("👋 智能体退出")
|
print("\n\n👋 收到退出信号,智能体停止运行")
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ 错误: {e}")
|
print(f"❌ 运行时错误: {e}")
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
# 使用示例
|
# 使用示例
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
agent = TaskAgent("示例智能体")
|
agent = TaskAgent("Python智能体")
|
||||||
agent.run()
|
agent.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -368,7 +564,7 @@ class TaskAgent {
|
|||||||
const result = await this.executeTask(task);
|
const result = await this.executeTask(task);
|
||||||
|
|
||||||
await this.updateTask(task.id, 'completed', result);
|
await this.updateTask(task.id, 'completed', result);
|
||||||
console.log(`✅ 任务完成: ${task.id}`);
|
console.log(`✅ 任务完成`);
|
||||||
|
|
||||||
await this.heartbeat('idle');
|
await this.heartbeat('idle');
|
||||||
}
|
}
|
||||||
@@ -384,35 +580,71 @@ class TaskAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 使用示例
|
// 使用示例
|
||||||
const agent = new TaskAgent('示例智能体');
|
const agent = new TaskAgent('JS智能体');
|
||||||
agent.run().catch(console.error);
|
agent.run().catch(console.error);
|
||||||
```
|
```
|
||||||
|
|
||||||
## 数据库结构
|
## 数据库结构
|
||||||
|
|
||||||
系统使用 SQLite 数据库,包含以下表:
|
|
||||||
|
|
||||||
### agents 表
|
### agents 表
|
||||||
- `id` - 智能体唯一标识
|
| 字段 | 类型 | 说明 |
|
||||||
- `name` - 智能体名称
|
|------|------|------|
|
||||||
- `type` - 智能体类型
|
| id | VARCHAR(255) | 智能体唯一标识(UUID) |
|
||||||
- `capabilities` - 能力列表(JSON)
|
| name | VARCHAR(255) | 智能体名称 |
|
||||||
- `status` - 状态(idle/busy)
|
| type | VARCHAR(100) | 智能体类型 |
|
||||||
- `registered_at` - 注册时间
|
| capabilities | JSONB | 能力列表(JSON) |
|
||||||
- `last_heartbeat` - 最后心跳时间
|
| status | VARCHAR(50) | 状态(idle/busy) |
|
||||||
|
| registered_at | BIGINT | 注册时间(毫秒时间戳) |
|
||||||
|
| last_heartbeat | BIGINT | 最后心跳时间(毫秒时间戳) |
|
||||||
|
|
||||||
### tasks 表
|
### tasks 表
|
||||||
- `id` - 任务唯一标识
|
| 字段 | 类型 | 说明 |
|
||||||
- `title` - 任务标题
|
|------|------|------|
|
||||||
- `description` - 任务描述
|
| id | VARCHAR(255) | 任务唯一标识(UUID) |
|
||||||
- `priority` - 优先级(low/normal/high)
|
| title | VARCHAR(500) | 任务标题 |
|
||||||
- `status` - 状态(pending/in_progress/completed)
|
| description | TEXT | 任务描述 |
|
||||||
- `created_by` - 创建者
|
| priority | VARCHAR(50) | 优先级(low/normal/high) |
|
||||||
- `assigned_to` - 分配给的智能体
|
| status | VARCHAR(50) | 状态(pending/in_progress/completed) |
|
||||||
- `created_at` - 创建时间
|
| created_by | VARCHAR(255) | 创建者 |
|
||||||
- `updated_at` - 更新时间
|
| assigned_to | VARCHAR(255) | 分配给的智能体 |
|
||||||
- `completed_at` - 完成时间
|
| created_at | BIGINT | 创建时间(毫秒时间戳) |
|
||||||
- `result` - 任务结果
|
| updated_at | BIGINT | 更新时间(毫秒时间戳) |
|
||||||
|
| completed_at | BIGINT | 完成时间(毫秒时间戳) |
|
||||||
|
| result | TEXT | 任务结果 |
|
||||||
|
|
||||||
|
## 架构说明
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ 智能体 1 │ │ 智能体 2 │ │ 智能体 N │
|
||||||
|
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
|
||||||
|
│ │ │
|
||||||
|
│ 注册/心跳/领取 │ │
|
||||||
|
└────────────┬───────┴────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ API 服务 (Node.js) │
|
||||||
|
│ - RESTful API │
|
||||||
|
│ - 任务调度 │
|
||||||
|
│ - 状态管理 │
|
||||||
|
└──────────┬───────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────┐
|
||||||
|
│ PostgreSQL 数据库 │
|
||||||
|
│ - agents 表 │
|
||||||
|
│ - tasks 表 │
|
||||||
|
└──────────────────────┘
|
||||||
|
▲
|
||||||
|
│
|
||||||
|
┌──────────┴───────────┐
|
||||||
|
│ Web 管理界面 │
|
||||||
|
│ - 实时统计 │
|
||||||
|
│ - 任务列表 │
|
||||||
|
│ - 人工创建任务 │
|
||||||
|
└──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
## 扩展建议
|
## 扩展建议
|
||||||
|
|
||||||
@@ -423,6 +655,31 @@ agent.run().catch(console.error);
|
|||||||
5. **日志记录** - 详细的操作日志
|
5. **日志记录** - 详细的操作日志
|
||||||
6. **性能监控** - 任务执行时间和成功率统计
|
6. **性能监控** - 任务执行时间和成功率统计
|
||||||
7. **分布式部署** - 支持多节点部署
|
7. **分布式部署** - 支持多节点部署
|
||||||
|
8. **任务超时** - 自动检测和重新分配超时任务
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
### 数据库连接失败
|
||||||
|
```bash
|
||||||
|
# 检查 PostgreSQL 是否运行
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# 查看数据库日志
|
||||||
|
docker-compose logs postgres
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
docker-compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### 智能体注册失败
|
||||||
|
- 检查服务是否正常运行
|
||||||
|
- 确认网络连接正常
|
||||||
|
- 查看应用日志: `docker-compose logs app`
|
||||||
|
|
||||||
|
### 任务领取失败
|
||||||
|
- 确认任务状态为 `pending`
|
||||||
|
- 确认智能体 ID 正确
|
||||||
|
- 任务可能已被其他智能体领取
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# PostgreSQL 数据库
|
||||||
|
postgres:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: agent-task-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: agent_tasks
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- agent-network
|
||||||
|
|
||||||
|
# 应用服务
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: agent-task-service
|
||||||
|
environment:
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_NAME: agent_tasks
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_PASSWORD: postgres
|
||||||
|
PORT: 3000
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- agent-network
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
agent-network:
|
||||||
|
driver: bridge
|
||||||
Generated
+153
-6
@@ -7,13 +7,14 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "agent-task-service",
|
"name": "agent-task-service",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^2.2.2",
|
"body-parser": "^2.2.2",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
|
"pg": "^8.18.0",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^9.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@gar/promisify": {
|
"node_modules/@gar/promisify": {
|
||||||
@@ -1527,6 +1528,134 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
|
||||||
|
"integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-connection-string": "^2.11.0",
|
||||||
|
"pg-pool": "^3.11.0",
|
||||||
|
"pg-protocol": "^1.11.0",
|
||||||
|
"pg-types": "2.2.0",
|
||||||
|
"pgpass": "1.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz",
|
||||||
|
"integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz",
|
||||||
|
"integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prebuild-install": {
|
"node_modules/prebuild-install": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
@@ -1969,6 +2098,15 @@
|
|||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sqlite3": {
|
"node_modules/sqlite3": {
|
||||||
"version": "5.1.7",
|
"version": "5.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz",
|
||||||
@@ -2193,16 +2331,16 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "13.0.0",
|
"version": "9.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||||
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
"https://github.com/sponsors/broofa",
|
"https://github.com/sponsors/broofa",
|
||||||
"https://github.com/sponsors/ctavan"
|
"https://github.com/sponsors/ctavan"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"uuid": "dist-node/bin/uuid"
|
"uuid": "dist/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
@@ -2246,6 +2384,15 @@
|
|||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/xtend": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
|
|||||||
+10
-7
@@ -1,19 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "agent-task-service",
|
"name": "agent-task-service",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "多智能体任务管理系统",
|
||||||
"main": "index.js",
|
"main": "server-postgres.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"start": "node server-postgres.js",
|
||||||
|
"dev": "node server-postgres.js",
|
||||||
|
"sqlite": "node server.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": ["multi-agent", "task-management", "ai"],
|
||||||
"author": "",
|
"author": "work-1",
|
||||||
"license": "ISC",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^2.2.2",
|
"body-parser": "^2.2.2",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
|
"pg": "^8.18.0",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^9.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,382 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
const cors = require('cors');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// 中间件
|
||||||
|
app.use(cors());
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
// PostgreSQL 连接池
|
||||||
|
const pool = new Pool({
|
||||||
|
host: process.env.DB_HOST || 'postgres',
|
||||||
|
port: process.env.DB_PORT || 5432,
|
||||||
|
database: process.env.DB_NAME || 'agent_tasks',
|
||||||
|
user: process.env.DB_USER || 'postgres',
|
||||||
|
password: process.env.DB_PASSWORD || 'postgres',
|
||||||
|
max: 20,
|
||||||
|
idleTimeoutMillis: 30000,
|
||||||
|
connectionTimeoutMillis: 2000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试数据库连接
|
||||||
|
pool.connect((err, client, release) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('❌ 数据库连接失败:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log('✅ 已连接到 PostgreSQL 数据库');
|
||||||
|
release();
|
||||||
|
initDatabase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建表结构
|
||||||
|
async function initDatabase() {
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
// 智能体表
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS agents (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
type VARCHAR(100),
|
||||||
|
capabilities JSONB,
|
||||||
|
status VARCHAR(50) DEFAULT 'idle',
|
||||||
|
registered_at BIGINT,
|
||||||
|
last_heartbeat BIGINT
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 任务表
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS tasks (
|
||||||
|
id VARCHAR(255) PRIMARY KEY,
|
||||||
|
title VARCHAR(500) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
priority VARCHAR(50) DEFAULT 'normal',
|
||||||
|
status VARCHAR(50) DEFAULT 'pending',
|
||||||
|
created_by VARCHAR(255),
|
||||||
|
assigned_to VARCHAR(255),
|
||||||
|
created_at BIGINT,
|
||||||
|
updated_at BIGINT,
|
||||||
|
completed_at BIGINT,
|
||||||
|
result TEXT
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
// 创建索引
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status)
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to)
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query('COMMIT');
|
||||||
|
console.log('✅ 数据库表已初始化');
|
||||||
|
} catch (err) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
console.error('❌ 数据库初始化失败:', err);
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== API 路由 ====================
|
||||||
|
|
||||||
|
// 1. 智能体注册
|
||||||
|
app.post('/api/agents/register', async (req, res) => {
|
||||||
|
const { name, type, capabilities } = req.body;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return res.status(400).json({ error: '智能体名称不能为空' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = uuidv4();
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO agents (id, name, type, capabilities, status, registered_at, last_heartbeat)
|
||||||
|
VALUES ($1, $2, $3, $4, 'idle', $5, $6)`,
|
||||||
|
[id, name, type || 'general', JSON.stringify(capabilities || []), now, now]
|
||||||
|
);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type: type || 'general',
|
||||||
|
status: 'idle',
|
||||||
|
message: '智能体注册成功'
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 获取所有智能体
|
||||||
|
app.get('/api/agents', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const result = await pool.query('SELECT * FROM agents ORDER BY registered_at DESC');
|
||||||
|
res.json(result.rows.map(row => ({
|
||||||
|
...row,
|
||||||
|
capabilities: row.capabilities || []
|
||||||
|
})));
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 更新智能体状态(心跳)
|
||||||
|
app.post('/api/agents/:id/heartbeat', async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { status } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await pool.query(
|
||||||
|
'UPDATE agents SET status = $1, last_heartbeat = $2 WHERE id = $3',
|
||||||
|
[status || 'idle', Date.now(), id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.rowCount === 0) {
|
||||||
|
return res.status(404).json({ error: '智能体不存在' });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ message: '心跳更新成功' });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 创建任务
|
||||||
|
app.post('/api/tasks', async (req, res) => {
|
||||||
|
const { title, description, priority, created_by } = req.body;
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
return res.status(400).json({ error: '任务标题不能为空' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = uuidv4();
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pool.query(
|
||||||
|
`INSERT INTO tasks (id, title, description, priority, status, created_by, created_at, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7)`,
|
||||||
|
[id, title, description || '', priority || 'normal', created_by || 'human', now, now]
|
||||||
|
);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
status: 'pending',
|
||||||
|
message: '任务创建成功'
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. 获取所有任务
|
||||||
|
app.get('/api/tasks', async (req, res) => {
|
||||||
|
const { status } = req.query;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let query = 'SELECT * FROM tasks';
|
||||||
|
let params = [];
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
query += ' WHERE status = $1';
|
||||||
|
params.push(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
query += ' ORDER BY created_at DESC';
|
||||||
|
|
||||||
|
const result = await pool.query(query, params);
|
||||||
|
res.json(result.rows);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. 领取任务
|
||||||
|
app.post('/api/tasks/:id/claim', async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { agent_id } = req.body;
|
||||||
|
|
||||||
|
if (!agent_id) {
|
||||||
|
return res.status(400).json({ error: '需要提供智能体ID' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
const taskResult = await client.query('SELECT status FROM tasks WHERE id = $1', [id]);
|
||||||
|
|
||||||
|
if (taskResult.rows.length === 0) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
return res.status(404).json({ error: '任务不存在' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskResult.rows[0].status !== 'pending') {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
return res.status(400).json({ error: '任务已被领取或已完成' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
'UPDATE tasks SET status = $1, assigned_to = $2, updated_at = $3 WHERE id = $4',
|
||||||
|
['in_progress', agent_id, Date.now(), id]
|
||||||
|
);
|
||||||
|
|
||||||
|
await client.query('UPDATE agents SET status = $1 WHERE id = $2', ['busy', agent_id]);
|
||||||
|
|
||||||
|
await client.query('COMMIT');
|
||||||
|
res.json({ message: '任务领取成功' });
|
||||||
|
} catch (err) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 7. 更新任务状态
|
||||||
|
app.put('/api/tasks/:id', async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { status, result } = req.body;
|
||||||
|
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const updates = [];
|
||||||
|
const params = [];
|
||||||
|
let paramIndex = 1;
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
updates.push(`status = $${paramIndex++}`);
|
||||||
|
params.push(status);
|
||||||
|
}
|
||||||
|
if (result !== undefined) {
|
||||||
|
updates.push(`result = $${paramIndex++}`);
|
||||||
|
params.push(result);
|
||||||
|
}
|
||||||
|
if (status === 'completed') {
|
||||||
|
updates.push(`completed_at = $${paramIndex++}`);
|
||||||
|
params.push(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.push(`updated_at = $${paramIndex++}`);
|
||||||
|
params.push(now);
|
||||||
|
params.push(id);
|
||||||
|
|
||||||
|
const updateResult = await client.query(
|
||||||
|
`UPDATE tasks SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updateResult.rowCount === 0) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
return res.status(404).json({ error: '任务不存在' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果任务完成,将智能体状态改为 idle
|
||||||
|
if (status === 'completed') {
|
||||||
|
const taskResult = await client.query('SELECT assigned_to FROM tasks WHERE id = $1', [id]);
|
||||||
|
if (taskResult.rows.length > 0 && taskResult.rows[0].assigned_to) {
|
||||||
|
await client.query('UPDATE agents SET status = $1 WHERE id = $2', ['idle', taskResult.rows[0].assigned_to]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query('COMMIT');
|
||||||
|
res.json({ message: '任务状态更新成功' });
|
||||||
|
} catch (err) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 8. 获取单个任务详情
|
||||||
|
app.get('/api/tasks/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const result = await pool.query('SELECT * FROM tasks WHERE id = $1', [req.params.id]);
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
return res.status(404).json({ error: '任务不存在' });
|
||||||
|
}
|
||||||
|
res.json(result.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 9. 统计信息
|
||||||
|
app.get('/api/stats', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const stats = {
|
||||||
|
total_agents: 0,
|
||||||
|
active_agents: 0,
|
||||||
|
total_tasks: 0,
|
||||||
|
pending_tasks: 0,
|
||||||
|
in_progress_tasks: 0,
|
||||||
|
completed_tasks: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = await Promise.all([
|
||||||
|
pool.query('SELECT COUNT(*) as count FROM agents'),
|
||||||
|
pool.query('SELECT COUNT(*) as count FROM agents WHERE status = $1', ['busy']),
|
||||||
|
pool.query('SELECT COUNT(*) as count FROM tasks'),
|
||||||
|
pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['pending']),
|
||||||
|
pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['in_progress']),
|
||||||
|
pool.query('SELECT COUNT(*) as count FROM tasks WHERE status = $1', ['completed'])
|
||||||
|
]);
|
||||||
|
|
||||||
|
stats.total_agents = parseInt(results[0].rows[0].count);
|
||||||
|
stats.active_agents = parseInt(results[1].rows[0].count);
|
||||||
|
stats.total_tasks = parseInt(results[2].rows[0].count);
|
||||||
|
stats.pending_tasks = parseInt(results[3].rows[0].count);
|
||||||
|
stats.in_progress_tasks = parseInt(results[4].rows[0].count);
|
||||||
|
stats.completed_tasks = parseInt(results[5].rows[0].count);
|
||||||
|
|
||||||
|
res.json(stats);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 健康检查
|
||||||
|
app.get('/health', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await pool.query('SELECT 1');
|
||||||
|
res.json({ status: 'healthy', database: 'connected' });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(503).json({ status: 'unhealthy', error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`🚀 多智能体任务服务已启动: http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 优雅关闭
|
||||||
|
process.on('SIGINT', async () => {
|
||||||
|
console.log('\n正在关闭服务...');
|
||||||
|
await pool.end();
|
||||||
|
console.log('数据库连接已关闭');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user