mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-09-29 00:32:06 +08:00
* ♻️ refactor(core): 重构 AI 能力与定时任务调度系统 - 【AI 能力与工具】重构 Capability 注册与管理机制,引入 CapabilityManager 统一管理 - 移除全局能力注册表,改用声明式装饰器 `@capability` 进行解耦注册 - 重构工具解析器链,使用统一的 BaseToolResolver 代替原有的多个特定解析器 - 增强工具查询过滤,支持通配符匹配、工具箱过滤和排除标签 - 【定时任务调度】重构定时任务管理器,引入 SchedulerRegistry 统一管理任务元数据 - 引入 JobConfig 聚合定时任务配置,支持用户维度的定时任务调度 - 重构执行分发器,支持并发限制、串行间隔和随机延迟打散 - 【运行上下文】引入 ScheduledDeps 以支持后台和定时任务环境下的依赖注入 - 优化 RunContext,支持从定时任务上下文快速构造,并提供 emit 辅助方法 - 【日志与监控】引入 AILoggerProxy,实现 AI 各模块的专属日志输出 - 将各模块的全局 logger 替换为对应的模块专属日志代理 - 【其他优化】修复 Pydantic V1 兼容层中 model_validator 的装饰器兼容性问题 - 在非交互式环境(如定时任务)中自动隐藏 HITL 交互工具以节省 Token * ♻️ refactor(core): 优化内部导入路径并提升 Pydantic 兼容性 - 【重构】将 `services/ai` 模块内的绝对导入重构为相对导入,优化包结构 - 【重构】移除不必要的 `if TYPE_CHECKING` 保护,通过 `from __future__ import annotations` 直接导入类型 - 【清理】清理 `core/messages/types.py` 中未使用的 `AssistantContentUnion` 等联合类型定义 - 【优化】在 `utils/pydantic_compat.py` 中新增 `model_rebuild` 兼容函数,统一 Pydantic V1/V2 的模型重建逻辑 - 【优化】将部分函数内部的延迟导入提升至模块顶部,规范代码结构 * ♻️ refactor(imports): 优化导入路径为相对导入并清理冗余导入 - 【重构】将 AI 服务相关模块中的绝对导入路径修改为相对导入,提升模块内聚性与可移植性 - 【清理】移除多处函数内部或类方法中未使用的冗余导入,避免循环引用和资源浪费 - 【格式化】微调部分工具装饰器和返回语句的格式与尾随逗号 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
195 lines
7.9 KiB
Python
195 lines
7.9 KiB
Python
from typing import Annotated, Any
|
||
|
||
from pydantic import Field
|
||
|
||
from zhenxun.services.ai.flow.base import BaseRunnable
|
||
from zhenxun.services.ai.run.context import RunContext
|
||
from zhenxun.services.ai.tools.core.decorators import tool
|
||
from zhenxun.services.ai.tools.core.toolkit import BaseToolkit
|
||
from zhenxun.services.ai.tools.models import EndRunResult, ToolResult
|
||
from zhenxun.services.ai.utils.logger import log_team as logger
|
||
|
||
from .models import TaskBoardState, TaskNodeStatus
|
||
|
||
|
||
class TaskPlanningToolkit(BaseToolkit):
|
||
"""
|
||
任务规划工具箱 (Planner Toolkit)。
|
||
大模型专用的黑板操作工具。大模型被剥夺了执行权,仅能拆解、指派和总结任务。
|
||
"""
|
||
|
||
default_prefix = ""
|
||
|
||
default_instructions = """<instructions>
|
||
## 🛠️ 任务规划工作流指南
|
||
你现在的角色是**项目经理 (Planner)**。你的唯一职责是拆解任务、分配人员并监控看板状态,**系统底层会自动拉起专家执行任务**。
|
||
1. **规划**:使用 `create_task` 拆解任务,设定 `assignee`(专家名称)和 `depends_on`(依赖的其它任务ID)。
|
||
2. **等待与监控**:每次你创建或更新任务后,请立刻停止工具调用,系统引擎会自动并发执行 pending 任务并再次唤醒你。
|
||
3. **🩹 智能自愈与重试**:如果你被唤醒后,看到看板上有任务处于 `failed` 状态,
|
||
请仔细阅读失败结果 (result)。你可以通过 `update_task_status` 将该任务的状态重新修改为 `pending`
|
||
以触发重新执行(可以附带修改建议在 result 里),或者创建新任务替代它。
|
||
4. **终结**:当你确认所有目标已达成时,调用 `mark_all_complete` 附上最终总结,正式结束整个流水线。
|
||
⚠️ 警告:你没有任何执行具体业务代码或查询的工具,你只能操作任务看板!
|
||
</instructions>""" # noqa: E501
|
||
|
||
def __init__(self, members: list[BaseRunnable], **kwargs):
|
||
super().__init__(**kwargs)
|
||
self.members = members
|
||
|
||
def _get_board(self, context: RunContext) -> TaskBoardState:
|
||
"""从运行上下文中安全的获取或初始化任务看板状态"""
|
||
if "__task_board__" not in context.session.shared_state:
|
||
context.session.shared_state["__task_board__"] = TaskBoardState()
|
||
return context.session.shared_state["__task_board__"]
|
||
|
||
@tool(description="创建一个新任务并加入看板。")
|
||
async def create_task(
|
||
self,
|
||
title: Annotated[str, Field(description="任务的简短、可行动的标题")],
|
||
description: Annotated[
|
||
str, Field(description="详细的任务说明,告诉执行者需要做什么以及期望的产出")
|
||
],
|
||
assignee: Annotated[
|
||
str,
|
||
Field(
|
||
description=(
|
||
"负责执行此任务的专家名称,必须完全匹配 <team_members> "
|
||
"中提供的 id,严禁捏造"
|
||
)
|
||
),
|
||
],
|
||
context: RunContext,
|
||
depends_on: Annotated[
|
||
list[str],
|
||
Field(
|
||
description=(
|
||
"该任务依赖的前置任务的【标题(title)】列表"
|
||
"(因同一回合创建时未知ID,请务必使用前置任务的 title 作为依赖)。"
|
||
"无依赖则必须传入空数组 []"
|
||
),
|
||
),
|
||
],
|
||
metadata: Annotated[
|
||
dict[str, Any],
|
||
Field(
|
||
description="可选的附加字典,用于向执行专家传递额外的结构化约束或参数"
|
||
),
|
||
] = {},
|
||
) -> ToolResult:
|
||
board = self._get_board(context)
|
||
|
||
valid_member_names = [m.name for m in self.members]
|
||
if assignee not in valid_member_names:
|
||
return ToolResult(
|
||
output=(
|
||
f"❌ 创建失败:未找到名为 '{assignee}' 的专家。"
|
||
f"可用专家: {valid_member_names}"
|
||
)
|
||
).as_error()
|
||
|
||
task = board.create_task(
|
||
title=title,
|
||
description=description,
|
||
assignee=assignee,
|
||
dependencies=depends_on,
|
||
metadata=metadata,
|
||
)
|
||
|
||
logger.debug(f" 🆕 [新建任务] `{task.title}` -> 👨💼{task.assignee}")
|
||
|
||
board_str = board.render_board_to_string()
|
||
return ToolResult(
|
||
output=(
|
||
f"✅ 任务创建成功!任务 ID: [{task.id}],"
|
||
f"状态: {task.status.value}\n\n{board_str}"
|
||
)
|
||
)
|
||
|
||
@tool(
|
||
description=(
|
||
"手动强制更新任务的状态(仅在特殊情况下使用,"
|
||
"因为 execute_task 会自动更新状态)。"
|
||
)
|
||
)
|
||
async def update_task_status(
|
||
self,
|
||
task_id: Annotated[str, Field(description="要更新的任务的唯一 ID")],
|
||
status: Annotated[
|
||
TaskNodeStatus,
|
||
Field(
|
||
description=(
|
||
"新的任务状态,支持: pending(用于重试), completed, failed 等"
|
||
)
|
||
),
|
||
],
|
||
context: RunContext,
|
||
result: Annotated[
|
||
str,
|
||
Field(
|
||
description=(
|
||
"提供结果、失败原因,或在设为 pending 重试时给执行专家的建议"
|
||
)
|
||
),
|
||
] = "",
|
||
) -> ToolResult:
|
||
board = self._get_board(context)
|
||
|
||
if status == TaskNodeStatus.in_progress:
|
||
return ToolResult(
|
||
output=(
|
||
"❌ 权限拒绝:你不能手动将任务状态设置为 in_progress。"
|
||
"该状态由底层执行引擎自动管理。如果你想让任务重新执行,"
|
||
"请将其设置为 pending。"
|
||
)
|
||
).as_error()
|
||
|
||
updated = board.update_task_status(task_id, status, result if result else None)
|
||
if not updated:
|
||
return ToolResult(output=f"❌ 找不到 ID 为 '{task_id}' 的任务。").as_error()
|
||
|
||
task_obj = board.get_task(task_id)
|
||
task_title = task_obj.title if task_obj else "Unknown"
|
||
logger.debug(f" 🔄 [任务状态变更] `{task_title}` -> {status.value}")
|
||
|
||
if task_obj and task_obj.status != status:
|
||
board_str = board.render_board_to_string()
|
||
return ToolResult(
|
||
output=(
|
||
"❌ 状态更新失败(触发底层状态机防呆回滚)!\n"
|
||
f"你尝试将 [{task_id}] 强制设置为 {status.value},"
|
||
f"但系统计算依赖图后将其重置为了 {task_obj.status.value}。\n"
|
||
"💡 原因分析:它的前置依赖(depends_on)可能尚未 COMPLETED,"
|
||
"或者你填错了依赖项的名称/ID导致系统无法追踪。\n\n"
|
||
f"{board_str}"
|
||
)
|
||
).as_error()
|
||
|
||
board_str = board.render_board_to_string()
|
||
return ToolResult(
|
||
output=f"✅ 任务 [{task_id}] 已更新为 {status.value}。\n\n{board_str}"
|
||
)
|
||
|
||
@tool(
|
||
description=(
|
||
"声明整体目标已完成。在调用此工具后,"
|
||
"大模型将被立刻中断并直接将 summary 返回给用户。"
|
||
)
|
||
)
|
||
async def mark_all_complete(
|
||
self,
|
||
summary: Annotated[
|
||
str,
|
||
Field(
|
||
description=(
|
||
"流程的最终战报。⚠️ 必须在总结中完整包含各专家产出的"
|
||
"核心交付物原文(如生成的故事、最终翻译内容等),绝对不能只说“已完成”!"
|
||
)
|
||
),
|
||
],
|
||
context: RunContext,
|
||
) -> ToolResult:
|
||
board = self._get_board(context)
|
||
board.is_goal_complete = True
|
||
board.final_summary = summary
|
||
return EndRunResult(output=summary)
|