mirror of
https://github.com/countbot-ai/CountBot.git
synced 2026-09-14 20:46:47 +08:00
14d4d4e2a0
功能(核心): 会话级配置系统与前端体验全面升级 核心功能:- 会话级配置系统,轻松打造你的AI团队(每个会话独立 API、模型、提示词) - 新增微博、企业微信、小智 AI 三个 IM 渠道 - 实现企业微信、飞书两个IM渠道的消息流式输出 - 全面优化多智能体协作系统 - 全面优化项目代码兼容性 - 新增 /help 命令(通过 IM 渠道输入即可体验) - 集成 Mermaid 图表渲染引擎 - 全面兼容 OpenClaw Skills 技能生态 - Heartbeat 主动问候系统重构 - WEB聊天页面支持自定义用户头像 - 支持自定义输出语言,比如中文、英文、西语等 - 修复大量已知问题
316 lines
9.3 KiB
Python
316 lines
9.3 KiB
Python
"""Tasks API - 子 Agent 任务管理"""
|
||
|
||
from typing import Dict, List, Optional
|
||
from fastapi import APIRouter, HTTPException, status, Depends
|
||
from loguru import logger
|
||
from pydantic import BaseModel, Field
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from backend.database import get_db
|
||
|
||
router = APIRouter(prefix="/api/tasks", tags=["tasks"])
|
||
|
||
|
||
# ============================================================================
|
||
# Request/Response Models
|
||
# ============================================================================
|
||
|
||
|
||
class TaskResponse(BaseModel):
|
||
"""任务响应"""
|
||
|
||
task_id: str
|
||
label: str
|
||
message: str
|
||
session_id: Optional[str]
|
||
status: str
|
||
progress: int
|
||
result: Optional[str]
|
||
error: Optional[str]
|
||
created_at: str
|
||
started_at: Optional[str]
|
||
completed_at: Optional[str]
|
||
tool_call_records: List[dict] = []
|
||
|
||
|
||
class TaskStatsResponse(BaseModel):
|
||
"""任务统计响应"""
|
||
|
||
total: int
|
||
pending: int
|
||
running: int
|
||
completed: int
|
||
failed: int
|
||
cancelled: int
|
||
|
||
|
||
# ============================================================================
|
||
# Get SubagentManager from chat API
|
||
# ============================================================================
|
||
|
||
def get_subagent_manager():
|
||
"""获取 SubagentManager 实例(可能为 None)"""
|
||
from backend.api.chat import get_global_subagent_manager
|
||
return get_global_subagent_manager()
|
||
|
||
|
||
def require_subagent_manager():
|
||
"""获取 SubagentManager 实例;若未初始化则返回 404(不触发客户端重试)"""
|
||
manager = get_subagent_manager()
|
||
if manager is None:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="No tasks available. SubagentManager has not been initialized yet."
|
||
)
|
||
return manager
|
||
|
||
|
||
# ============================================================================
|
||
# Tasks Endpoints
|
||
# ============================================================================
|
||
|
||
|
||
@router.get("/", response_model=List[TaskResponse])
|
||
async def list_tasks(
|
||
status_filter: Optional[str] = None,
|
||
session_id: Optional[str] = None,
|
||
) -> List[TaskResponse]:
|
||
"""
|
||
列出所有任务
|
||
|
||
Args:
|
||
status_filter: 状态过滤(pending, running, completed, failed, cancelled)
|
||
session_id: 会话 ID 过滤
|
||
|
||
Returns:
|
||
List[TaskResponse]: 任务列表
|
||
"""
|
||
try:
|
||
manager = require_subagent_manager()
|
||
|
||
# 解析状态过滤
|
||
from backend.modules.agent.subagent import TaskStatus
|
||
status_enum = None
|
||
if status_filter:
|
||
try:
|
||
status_enum = TaskStatus(status_filter)
|
||
except ValueError:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail=f"Invalid status: {status_filter}"
|
||
)
|
||
|
||
# 获取任务列表
|
||
tasks = manager.list_tasks(status=status_enum, session_id=session_id)
|
||
|
||
# 转换为响应模型
|
||
return [
|
||
TaskResponse(
|
||
task_id=task.task_id,
|
||
label=task.label,
|
||
message=task.message,
|
||
session_id=task.session_id,
|
||
status=task.status.value,
|
||
progress=task.progress,
|
||
result=task.result,
|
||
error=task.error,
|
||
created_at=task.created_at.isoformat(),
|
||
started_at=task.started_at.isoformat() if task.started_at else None,
|
||
completed_at=task.completed_at.isoformat() if task.completed_at else None,
|
||
tool_call_records=task.tool_call_records,
|
||
)
|
||
for task in tasks
|
||
]
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.exception(f"Failed to list tasks: {e}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Failed to list tasks: {str(e)}"
|
||
)
|
||
|
||
|
||
@router.get("/stats", response_model=TaskStatsResponse)
|
||
async def get_task_stats() -> TaskStatsResponse:
|
||
"""
|
||
获取任务统计信息
|
||
|
||
Returns:
|
||
TaskStatsResponse: 统计信息
|
||
"""
|
||
try:
|
||
manager = require_subagent_manager()
|
||
stats = manager.get_stats()
|
||
|
||
return TaskStatsResponse(**stats)
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.exception(f"Failed to get task stats: {e}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Failed to get task stats: {str(e)}"
|
||
)
|
||
|
||
|
||
@router.get("/{task_id}", response_model=TaskResponse)
|
||
async def get_task(task_id: str, db: AsyncSession = Depends(get_db)) -> TaskResponse:
|
||
"""
|
||
获取任务详情
|
||
|
||
Args:
|
||
task_id: 任务 ID
|
||
db: 数据库会话
|
||
|
||
Returns:
|
||
TaskResponse: 任务详情
|
||
|
||
Raises:
|
||
HTTPException: 任务不存在
|
||
"""
|
||
try:
|
||
manager = require_subagent_manager()
|
||
|
||
# 先从内存查询
|
||
task = manager.get_task(task_id)
|
||
|
||
# 如果内存中没有,从数据库查询
|
||
if not task:
|
||
from sqlalchemy import select
|
||
from backend.models.task import Task
|
||
|
||
result = await db.execute(
|
||
select(Task).where(Task.id == task_id)
|
||
)
|
||
db_task = result.scalar_one_or_none()
|
||
|
||
if not db_task:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"Task '{task_id}' not found"
|
||
)
|
||
|
||
# 从数据库任务构建响应
|
||
import json
|
||
tool_call_records = []
|
||
if db_task.tool_call_records:
|
||
try:
|
||
tool_call_records = json.loads(db_task.tool_call_records)
|
||
except json.JSONDecodeError:
|
||
pass
|
||
|
||
return TaskResponse(
|
||
task_id=db_task.id,
|
||
label=db_task.label,
|
||
message=db_task.message,
|
||
session_id=db_task.session_id,
|
||
status=db_task.status,
|
||
progress=db_task.progress,
|
||
result=db_task.result,
|
||
error=db_task.error,
|
||
created_at=db_task.created_at.isoformat(),
|
||
started_at=db_task.started_at.isoformat() if db_task.started_at else None,
|
||
completed_at=db_task.completed_at.isoformat() if db_task.completed_at else None,
|
||
tool_call_records=tool_call_records,
|
||
)
|
||
|
||
# 从内存任务返回
|
||
return TaskResponse(
|
||
task_id=task.task_id,
|
||
label=task.label,
|
||
message=task.message,
|
||
session_id=task.session_id,
|
||
status=task.status.value,
|
||
progress=task.progress,
|
||
result=task.result,
|
||
error=task.error,
|
||
created_at=task.created_at.isoformat(),
|
||
started_at=task.started_at.isoformat() if task.started_at else None,
|
||
completed_at=task.completed_at.isoformat() if task.completed_at else None,
|
||
tool_call_records=task.tool_call_records,
|
||
)
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.exception(f"Failed to get task: {e}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Failed to get task: {str(e)}"
|
||
)
|
||
|
||
|
||
@router.delete("/{task_id}")
|
||
async def cancel_task(task_id: str) -> Dict[str, bool]:
|
||
"""
|
||
取消任务
|
||
|
||
Args:
|
||
task_id: 任务 ID
|
||
|
||
Returns:
|
||
dict: 取消结果
|
||
|
||
Raises:
|
||
HTTPException: 任务不存在或无法取消
|
||
"""
|
||
try:
|
||
manager = require_subagent_manager()
|
||
success = await manager.cancel_task(task_id)
|
||
|
||
if not success:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail=f"Cannot cancel task '{task_id}' (not found or not running)"
|
||
)
|
||
|
||
return {"success": True}
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.exception(f"Failed to cancel task: {e}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Failed to cancel task: {str(e)}"
|
||
)
|
||
|
||
|
||
@router.post("/{task_id}/delete")
|
||
async def delete_task(task_id: str) -> Dict[str, bool]:
|
||
"""
|
||
删除任务
|
||
|
||
Args:
|
||
task_id: 任务 ID
|
||
|
||
Returns:
|
||
dict: 删除结果
|
||
|
||
Raises:
|
||
HTTPException: 任务不存在
|
||
"""
|
||
try:
|
||
manager = require_subagent_manager()
|
||
success = manager.delete_task(task_id)
|
||
|
||
if not success:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail=f"Task '{task_id}' not found"
|
||
)
|
||
|
||
return {"success": True}
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.exception(f"Failed to delete task: {e}")
|
||
raise HTTPException(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
detail=f"Failed to delete task: {str(e)}"
|
||
)
|