Main Agent 数字员工工厂基础设施: - 新增 Goal 和 Task SQLAlchemy 数据模型 - Agent 模型新增 agent_type / input_schema / output_schema - Execution 模型新增 goal_id 关联 - 新增 Goal/Task CRUD 服务层(含依赖检查、任务树、进度计算) - 新增 /api/v1/goals (9端点) + /api/v1/tasks (8端点) - 数据库迁移 013_add_goals_tasks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
369 lines
12 KiB
Python
369 lines
12 KiB
Python
"""
|
|
Goal & Task 业务服务层
|
|
"""
|
|
from sqlalchemy.orm import Session
|
|
from typing import List, Optional, Dict, Any
|
|
from datetime import datetime
|
|
import uuid
|
|
import logging
|
|
|
|
from app.models.goal import Goal
|
|
from app.models.task import Task
|
|
from app.core.exceptions import NotFoundError, ValidationError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ──────────────────────────── Goal CRUD ────────────────────────────
|
|
|
|
def create_goal(
|
|
db: Session,
|
|
creator_id: str,
|
|
title: str,
|
|
description: str = "",
|
|
priority: int = 5,
|
|
deadline: Optional[datetime] = None,
|
|
main_agent_id: Optional[str] = None,
|
|
parent_goal_id: Optional[str] = None,
|
|
autonomy_config: Optional[Dict[str, Any]] = None,
|
|
) -> Goal:
|
|
"""创建目标"""
|
|
goal = Goal(
|
|
id=str(uuid.uuid4()),
|
|
title=title,
|
|
description=description or "",
|
|
priority=priority,
|
|
deadline=deadline,
|
|
creator_id=creator_id,
|
|
main_agent_id=main_agent_id,
|
|
parent_goal_id=parent_goal_id,
|
|
autonomy_config=autonomy_config or {},
|
|
progress=0.0,
|
|
)
|
|
db.add(goal)
|
|
db.commit()
|
|
db.refresh(goal)
|
|
logger.info(f"Goal created: {goal.id} - {goal.title}")
|
|
return goal
|
|
|
|
|
|
def get_goal(db: Session, goal_id: str) -> Goal:
|
|
"""获取单个目标"""
|
|
goal = db.query(Goal).filter(Goal.id == goal_id).first()
|
|
if not goal:
|
|
raise NotFoundError("目标", goal_id)
|
|
return goal
|
|
|
|
|
|
def list_goals(
|
|
db: Session,
|
|
creator_id: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
skip: int = 0,
|
|
limit: int = 20,
|
|
) -> List[Goal]:
|
|
"""列出目标(支持筛选和分页)"""
|
|
q = db.query(Goal)
|
|
if creator_id:
|
|
q = q.filter(Goal.creator_id == creator_id)
|
|
if status:
|
|
q = q.filter(Goal.status == status)
|
|
return q.order_by(Goal.created_at.desc()).offset(skip).limit(limit).all()
|
|
|
|
|
|
def update_goal(
|
|
db: Session,
|
|
goal_id: str,
|
|
title: Optional[str] = None,
|
|
description: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
priority: Optional[int] = None,
|
|
plan: Optional[List[Dict[str, Any]]] = None,
|
|
autonomy_config: Optional[Dict[str, Any]] = None,
|
|
deadline: Optional[datetime] = None,
|
|
main_agent_id: Optional[str] = None,
|
|
) -> Goal:
|
|
"""更新目标"""
|
|
goal = get_goal(db, goal_id)
|
|
|
|
if title is not None:
|
|
goal.title = title
|
|
if description is not None:
|
|
goal.description = description
|
|
if status is not None:
|
|
valid_statuses = {"active", "paused", "completed", "failed", "cancelled"}
|
|
if status not in valid_statuses:
|
|
raise ValidationError(f"无效的状态: {status},有效值: {valid_statuses}")
|
|
goal.status = status
|
|
if status == "completed":
|
|
goal.completed_at = datetime.now()
|
|
goal.progress = 1.0
|
|
elif status == "failed" or status == "cancelled":
|
|
goal.completed_at = datetime.now()
|
|
if priority is not None:
|
|
if not 1 <= priority <= 10:
|
|
raise ValidationError("优先级必须在 1-10 之间")
|
|
goal.priority = priority
|
|
if plan is not None:
|
|
goal.plan = plan
|
|
if autonomy_config is not None:
|
|
goal.autonomy_config = autonomy_config
|
|
if deadline is not None:
|
|
goal.deadline = deadline
|
|
if main_agent_id is not None:
|
|
goal.main_agent_id = main_agent_id
|
|
|
|
db.commit()
|
|
db.refresh(goal)
|
|
return goal
|
|
|
|
|
|
def delete_goal(db: Session, goal_id: str) -> None:
|
|
"""删除目标及其所有任务"""
|
|
goal = get_goal(db, goal_id)
|
|
db.query(Task).filter(Task.goal_id == goal_id).delete()
|
|
db.delete(goal)
|
|
db.commit()
|
|
logger.info(f"Goal deleted: {goal_id}")
|
|
|
|
|
|
def update_goal_progress(db: Session, goal_id: str) -> Goal:
|
|
"""重新计算并更新目标进度"""
|
|
goal = get_goal(db, goal_id)
|
|
tasks = db.query(Task).filter(Task.goal_id == goal_id, Task.parent_task_id.is_(None)).all()
|
|
|
|
if not tasks:
|
|
goal.progress = 0.0
|
|
else:
|
|
completed_count = sum(1 for t in tasks if t.status == "completed")
|
|
goal.progress = round(completed_count / len(tasks), 4)
|
|
|
|
db.commit()
|
|
db.refresh(goal)
|
|
return goal
|
|
|
|
|
|
# ──────────────────────────── Task CRUD ────────────────────────────
|
|
|
|
def create_task(
|
|
db: Session,
|
|
goal_id: str,
|
|
title: str,
|
|
description: str = "",
|
|
priority: int = 5,
|
|
parent_task_id: Optional[str] = None,
|
|
depends_on: Optional[List[str]] = None,
|
|
assigned_agent_id: Optional[str] = None,
|
|
assigned_agent_name: Optional[str] = None,
|
|
task_config: Optional[Dict[str, Any]] = None,
|
|
deadline: Optional[datetime] = None,
|
|
requires_approval: bool = False,
|
|
approver_id: Optional[str] = None,
|
|
) -> Task:
|
|
"""创建任务"""
|
|
# 验证 goal 存在
|
|
get_goal(db, goal_id)
|
|
|
|
# 验证 depends_on 中的任务存在
|
|
if depends_on:
|
|
for dep_id in depends_on:
|
|
dep_task = db.query(Task).filter(Task.id == dep_id).first()
|
|
if not dep_task:
|
|
raise NotFoundError("前置依赖任务", dep_id)
|
|
|
|
task = Task(
|
|
id=str(uuid.uuid4()),
|
|
goal_id=goal_id,
|
|
title=title,
|
|
description=description or "",
|
|
priority=priority,
|
|
parent_task_id=parent_task_id,
|
|
depends_on=depends_on or [],
|
|
assigned_agent_id=assigned_agent_id,
|
|
assigned_agent_name=assigned_agent_name,
|
|
task_config=task_config or {},
|
|
deadline=deadline,
|
|
requires_approval=requires_approval,
|
|
approver_id=approver_id,
|
|
status="pending",
|
|
)
|
|
db.add(task)
|
|
db.commit()
|
|
db.refresh(task)
|
|
logger.info(f"Task created: {task.id} - {task.title} (goal={goal_id})")
|
|
return task
|
|
|
|
|
|
def get_task(db: Session, task_id: str) -> Task:
|
|
"""获取单个任务"""
|
|
task = db.query(Task).filter(Task.id == task_id).first()
|
|
if not task:
|
|
raise NotFoundError("任务", task_id)
|
|
return task
|
|
|
|
|
|
def list_tasks(
|
|
db: Session,
|
|
goal_id: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
assigned_agent_id: Optional[str] = None,
|
|
parent_task_id: Optional[str] = None,
|
|
skip: int = 0,
|
|
limit: int = 50,
|
|
) -> List[Task]:
|
|
"""列出任务(支持多条件筛选和分页)"""
|
|
q = db.query(Task)
|
|
if goal_id:
|
|
q = q.filter(Task.goal_id == goal_id)
|
|
if status:
|
|
q = q.filter(Task.status == status)
|
|
if assigned_agent_id:
|
|
q = q.filter(Task.assigned_agent_id == assigned_agent_id)
|
|
if parent_task_id is not None:
|
|
q = q.filter(Task.parent_task_id == parent_task_id)
|
|
return q.order_by(Task.created_at.asc()).offset(skip).limit(limit).all()
|
|
|
|
|
|
def update_task(
|
|
db: Session,
|
|
task_id: str,
|
|
title: Optional[str] = None,
|
|
description: Optional[str] = None,
|
|
status: Optional[str] = None,
|
|
priority: Optional[int] = None,
|
|
task_config: Optional[Dict[str, Any]] = None,
|
|
depends_on: Optional[List[str]] = None,
|
|
assigned_agent_id: Optional[str] = None,
|
|
assigned_agent_name: Optional[str] = None,
|
|
result: Optional[Dict[str, Any]] = None,
|
|
error_message: Optional[str] = None,
|
|
execution_id: Optional[str] = None,
|
|
deadline: Optional[datetime] = None,
|
|
) -> Task:
|
|
"""更新任务"""
|
|
task = get_task(db, task_id)
|
|
|
|
if title is not None:
|
|
task.title = title
|
|
if description is not None:
|
|
task.description = description
|
|
if status is not None:
|
|
valid_statuses = {"pending", "in_progress", "awaiting_approval", "completed", "failed", "cancelled"}
|
|
if status not in valid_statuses:
|
|
raise ValidationError(f"无效的任务状态: {status},有效值: {valid_statuses}")
|
|
task.status = status
|
|
if status == "in_progress" and task.started_at is None:
|
|
task.started_at = datetime.now()
|
|
elif status in ("completed", "failed", "cancelled"):
|
|
task.completed_at = datetime.now()
|
|
if priority is not None:
|
|
if not 1 <= priority <= 10:
|
|
raise ValidationError("优先级必须在 1-10 之间")
|
|
task.priority = priority
|
|
if task_config is not None:
|
|
task.task_config = task_config
|
|
if depends_on is not None:
|
|
task.depends_on = depends_on
|
|
if assigned_agent_id is not None:
|
|
task.assigned_agent_id = assigned_agent_id
|
|
if assigned_agent_name is not None:
|
|
task.assigned_agent_name = assigned_agent_name
|
|
if result is not None:
|
|
task.result = result
|
|
if error_message is not None:
|
|
task.error_message = error_message
|
|
if execution_id is not None:
|
|
task.execution_id = execution_id
|
|
if deadline is not None:
|
|
task.deadline = deadline
|
|
|
|
db.commit()
|
|
db.refresh(task)
|
|
|
|
# 任务状态变更时同步更新 Goal 进度
|
|
if status is not None:
|
|
update_goal_progress(db, task.goal_id)
|
|
|
|
return task
|
|
|
|
|
|
def delete_task(db: Session, task_id: str) -> None:
|
|
"""删除任务及其子任务"""
|
|
task = get_task(db, task_id)
|
|
goal_id = task.goal_id
|
|
# 递归删除子任务
|
|
db.query(Task).filter(Task.parent_task_id == task_id).delete()
|
|
db.delete(task)
|
|
db.commit()
|
|
# 更新进度
|
|
update_goal_progress(db, goal_id)
|
|
logger.info(f"Task deleted: {task_id}")
|
|
|
|
|
|
def get_task_dependencies_met(db: Session, task_id: str) -> bool:
|
|
"""检查任务的所有前置依赖是否已完成"""
|
|
task = get_task(db, task_id)
|
|
if not task.depends_on:
|
|
return True
|
|
for dep_id in task.depends_on:
|
|
dep_task = db.query(Task).filter(Task.id == dep_id).first()
|
|
if not dep_task or dep_task.status != "completed":
|
|
return False
|
|
return True
|
|
|
|
|
|
def get_goal_task_tree(db: Session, goal_id: str) -> Dict[str, Any]:
|
|
"""获取目标的任务树结构"""
|
|
goal = get_goal(db, goal_id)
|
|
tasks = list_tasks(db, goal_id=goal_id, limit=500)
|
|
|
|
# 构建任务树
|
|
task_map = {}
|
|
for t in tasks:
|
|
task_map[t.id] = {
|
|
"id": t.id,
|
|
"title": t.title,
|
|
"description": t.description,
|
|
"status": t.status,
|
|
"priority": t.priority,
|
|
"depends_on": t.depends_on,
|
|
"assigned_agent_id": t.assigned_agent_id,
|
|
"assigned_agent_name": t.assigned_agent_name,
|
|
"result": t.result,
|
|
"error_message": t.error_message,
|
|
"requires_approval": t.requires_approval,
|
|
"approval_status": t.approval_status,
|
|
"deadline": t.deadline.isoformat() if t.deadline else None,
|
|
"started_at": t.started_at.isoformat() if t.started_at else None,
|
|
"completed_at": t.completed_at.isoformat() if t.completed_at else None,
|
|
"children": [],
|
|
}
|
|
|
|
# 建立父子关系
|
|
roots = []
|
|
for t in tasks:
|
|
node = task_map[t.id]
|
|
if t.parent_task_id and t.parent_task_id in task_map:
|
|
task_map[t.parent_task_id]["children"].append(node)
|
|
else:
|
|
roots.append(node)
|
|
|
|
return {
|
|
"goal": {
|
|
"id": goal.id,
|
|
"title": goal.title,
|
|
"description": goal.description,
|
|
"status": goal.status,
|
|
"priority": goal.priority,
|
|
"progress": goal.progress,
|
|
"plan": goal.plan,
|
|
"autonomy_config": goal.autonomy_config,
|
|
"main_agent_id": goal.main_agent_id,
|
|
"deadline": goal.deadline.isoformat() if goal.deadline else None,
|
|
"started_at": goal.started_at.isoformat() if goal.started_at else None,
|
|
"completed_at": goal.completed_at.isoformat() if goal.completed_at else None,
|
|
"created_at": goal.created_at.isoformat() if goal.created_at else None,
|
|
},
|
|
"tasks": roots,
|
|
}
|