Files
zhenxun_bot/zhenxun/services/ai/flow/team/task_tools.py
T
52f7dbdedf ♻️ refactor(core): 重构 AI 编排框架与记忆及 RAG 子系统 (#2149)
* ♻️ refactor(core): 重构 AI 编排框架与记忆及 RAG 子系统

- 【重构】重构 `BaseRunnable` 并引入统一的 `RunIntent` 意图载体,规范 Agent、Team 和 Workflow 的执行流
- 【解耦】将中期记忆槽和长期向量记忆从 `MemoryConfig` 中解耦,转为独立的能力组件与工具箱进行管理
- 【记忆】移除 `MemoryReader` 和 `MemoryWriter`,统一封装为 `SessionMemoryContext` 会话记忆门面
- 【RAG】重构检索器与存储后端接口,统一采用 `QueryRequest` 进行多维度联合检索,并引入 `InMemoryScorer` 提升打分性能
- 【事件】优化 `EventBus` 异步事件分发机制,引入队列机制确保事件按序处理,避免并发竞态问题
- 【依赖注入】移除 `memory` 注入项,优化 `DependencyInjector` 的签名解析缓存以提升性能

* 🚨 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>
2026-07-14 16:48:33 +08:00

195 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Annotated, Any
from pydantic import Field
from zhenxun.services.ai.flow.core.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)