Files
aiagent/backend/app/services/goal_service.py
renjianbo 02d7cf8f62 feat: add Goal/Task data models, service layer, and API routes (Phase 1)
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>
2026-05-08 19:50:16 +08:00

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,
}