refactor(core): decouple actors and remove workflow templates (#67)

Removes the deprecated `workflow_template` concept entirely across both backend API routers, internal logic handling within the `supervisory_node` and `consciousness_node`, and front-end components. Enables `consciousness_node` to work autonomously.

Also refactors core package structure to enforce the "one python package, one Ray Actor" architectural rule. `GlobalWorkflowManager`, `WorkflowRunningEngine`, `PostgresDatabase`, and `WorkerCluster` have been moved to their own top-level decoupled package directories with properly exported `__init__.py` modules. Test suites have been relocated and import paths updated across the system.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: zhaoxi826 <198742034+zhaoxi826@users.noreply.github.com>
This commit is contained in:
2026-05-06 15:05:47 +08:00
committed by GitHub
parent b3ea4cd8d9
commit 209ba45477
97 changed files with 1872 additions and 1498 deletions
+27 -15
View File
@@ -20,23 +20,31 @@ from pretor.utils.agent_model import ResponseModel, InputModel, DepsModel
from pretor.utils.ray_hook import ray_actor_hook
from pretor.utils.logger import get_logger
logger = get_logger('worker_individual')
logger = get_logger("worker_individual")
class WorkerIndividualResponse(ResponseModel):
"""WorkerIndividualResponse 核心组件类。
这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """
这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。"""
output: str = Field(..., description="Worker执行任务的输出结果")
class WorkerIndividualDeps(DepsModel):
"""WorkerIndividualDeps 核心组件类。
这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """
这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。"""
task_event: dict
class WorkerIndividualInput(InputModel):
"""WorkerIndividualInput 核心组件类。
这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """
这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。"""
task_event: dict
class BaseIndividual:
"""
Worker Individual 的基类
@@ -51,14 +59,21 @@ class BaseIndividual:
"""完成 agent 模块的启动与依赖初始化。
在系统引导或服务拉起阶段被调用,负责建立网络连接、分配基础内存资源及注册核心服务组件。
Args: agent_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 system_prompt (str): 控制逻辑流向的具体字符串参数,指定了期望的 system prompt 内容。
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
from pretor.utils.get_tool import load_tools_from_list
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
provider_title = self.agent_config.get("provider_title", "openai") # default fallback
global_state_machine = ray_actor_hook(
"global_state_machine"
).global_state_machine
provider_title = self.agent_config.get(
"provider_title", "openai"
) # default fallback
model_id = self.agent_config.get("model_id", "gpt-4o") # default fallback
tools_list = self.agent_config.get("tools", None)
provider: Provider = await global_state_machine.get_provider.remote( provider_title)
provider: Provider = await global_state_machine.get_provider.remote(
provider_title
)
agent_factory = AgentFactory()
callables = load_tools_from_list(tools_list)
@@ -70,7 +85,7 @@ class BaseIndividual:
system_prompt=system_prompt,
deps_type=WorkerIndividualDeps,
agent_name=agent_name,
tools=callables
tools=callables,
)
@self.agent.system_prompt
@@ -78,17 +93,14 @@ class BaseIndividual:
"""执行与 dynamic prompt 相关的核心业务流转操作。
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
Args: ctx (RunContext[WorkerIndividualDeps]): 参与 dynamic prompt 逻辑运算或数据构建的上下文依赖对象。
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
prompt = system_prompt + "\n\n"
prompt += (
f"=== 当前任务上下文 ===\n"
f"{ctx.deps.task_event}\n"
)
prompt += f"=== 当前任务上下文 ===\n{ctx.deps.task_event}\n"
return prompt
async def run(self, task_event: dict) -> dict:
"""执行与 run 相关的核心业务流转操作。
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。"""
raise NotImplementedError("子类必须实现 run 方法")
@@ -12,10 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pretor.worker_individual.base_individual import BaseIndividual, WorkerIndividualDeps
from pretor.worker_individual.base_individual import (
BaseIndividual,
WorkerIndividualDeps,
)
from pretor.utils.logger import get_logger
logger = get_logger('ordinary_individual')
logger = get_logger("ordinary_individual")
class OrdinaryIndividual(BaseIndividual):
"""
@@ -29,18 +33,17 @@ class OrdinaryIndividual(BaseIndividual):
"""执行与 run 相关的核心业务流转操作。
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。"""
if self.agent is None:
system_prompt = self.agent_config.get("prompt", "你是一个普通的AI助手,请尽力完成给定的任务。")
system_prompt = self.agent_config.get(
"prompt", "你是一个普通的AI助手,请尽力完成给定的任务。"
)
await self._init_agent("ordinary_individual", system_prompt)
deps = WorkerIndividualDeps(task_event=task_event)
self.agent.retries = 3
try:
result = await self.agent.run(
f"请执行以下任务:\n{task_event}",
deps=deps
)
result = await self.agent.run(f"请执行以下任务:\n{task_event}", deps=deps)
return {"output": result.data.output}
except Exception as e:
logger.exception(f"OrdinaryIndividual {self.agent_id} 执行失败: {e}")
+30 -12
View File
@@ -12,14 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pretor.worker_individual.base_individual import BaseIndividual, WorkerIndividualDeps
from pretor.worker_individual.base_individual import (
BaseIndividual,
WorkerIndividualDeps,
)
from pretor.utils.logger import get_logger
import os
import json
from pydantic_ai import Tool
import importlib.util
logger = get_logger('skill_individual')
logger = get_logger("skill_individual")
class SkillIndividual(BaseIndividual):
"""
@@ -43,7 +47,9 @@ class SkillIndividual(BaseIndividual):
elif isinstance(bound_skill, dict):
skill_mapper = bound_skill
skill_base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "plugin", "skill"))
skill_base_dir = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "plugin", "skill")
)
for skill_name, _ in skill_mapper.items():
skill_path = os.path.join(skill_base_dir, skill_name)
@@ -52,7 +58,7 @@ class SkillIndividual(BaseIndividual):
continue
try:
with open(metadata_path, 'r', encoding='utf-8') as f:
with open(metadata_path, "r", encoding="utf-8") as f:
metadata = json.load(f)
except Exception as e:
logger.error(f"Failed to load metadata for skill {skill_name}: {e}")
@@ -72,18 +78,28 @@ class SkillIndividual(BaseIndividual):
func_name = func_info.get("name")
try:
# Dynamically load the python module
spec = importlib.util.spec_from_file_location(func_name, script_path)
spec = importlib.util.spec_from_file_location(
func_name, script_path
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
func = getattr(module, func_name)
if callable(func):
# Convert to PydanticAI Tool
tool = Tool(func, name=func_name, description=func_info.get("docstring", ""))
tool = Tool(
func,
name=func_name,
description=func_info.get("docstring", ""),
)
tools.append(tool)
logger.info(f"Loaded skill tool: {func_name} from {skill_name}")
logger.info(
f"Loaded skill tool: {func_name} from {skill_name}"
)
except Exception as e:
logger.error(f"Failed to load function {func_name} from {script_path}: {e}")
logger.error(
f"Failed to load function {func_name} from {script_path}: {e}"
)
return tools
@@ -91,10 +107,12 @@ class SkillIndividual(BaseIndividual):
"""执行与 run 相关的核心业务流转操作。
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。"""
if self.agent is None:
system_prompt = self.agent_config.get("prompt",
"你是一个拥有专业技能的专家级AI助手,请利用你的专业知识完成给定的任务。")
system_prompt = self.agent_config.get(
"prompt",
"你是一个拥有专业技能的专家级AI助手,请利用你的专业知识完成给定的任务。",
)
await self._init_agent("skill_individual", system_prompt)
deps = WorkerIndividualDeps(task_event=task_event)
@@ -106,7 +124,7 @@ class SkillIndividual(BaseIndividual):
result = await self.agent.run(
f"请执行以下任务:\n{task_event}",
deps=deps,
tools=tools if tools else None
tools=tools if tools else None,
)
return {"output": result.data.output}
except Exception as e:
+11 -8
View File
@@ -12,10 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from pretor.worker_individual.base_individual import BaseIndividual, WorkerIndividualDeps
from pretor.worker_individual.base_individual import (
BaseIndividual,
WorkerIndividualDeps,
)
from pretor.utils.logger import get_logger
logger = get_logger('special_individual')
logger = get_logger("special_individual")
class SpecialIndividual(BaseIndividual):
"""
@@ -29,18 +33,17 @@ class SpecialIndividual(BaseIndividual):
"""执行与 run 相关的核心业务流转操作。
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。"""
if self.agent is None:
system_prompt = self.agent_config.get("prompt", "你是一个特殊的AI助手,负责处理特殊类型的任务。")
system_prompt = self.agent_config.get(
"prompt", "你是一个特殊的AI助手,负责处理特殊类型的任务。"
)
await self._init_agent("special_individual", system_prompt)
deps = WorkerIndividualDeps(task_event=task_event)
self.agent.retries = 3
try:
result = await self.agent.run(
f"请执行以下任务:\n{task_event}",
deps=deps
)
result = await self.agent.run(f"请执行以下任务:\n{task_event}", deps=deps)
return {"output": result.data.output}
except Exception as e:
logger.exception(f"SpecialIndividual {self.agent_id} 执行失败: {e}")
-160
View File
@@ -1,160 +0,0 @@
# Copyright 2026 zhaoxi826
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ray
import time
import asyncio
from collections import OrderedDict
from ray.util.queue import Queue
from pretor.utils.ray_hook import ray_actor_hook
from pretor.worker_individual.base_individual import BaseIndividual
from pretor.worker_individual.skill_individual import SkillIndividual
from pretor.worker_individual.ordinary_individual import OrdinaryIndividual
from pretor.worker_individual.special_individual import SpecialIndividual
from pretor.utils.logger import get_logger
@ray.remote
class WorkerCluster:
"""
工作集群 Actor:管理和调度所有的 worker_individual
设计理念:按需加载,内存 LRU 淘汰,避免 Actor 爆炸
"""
def __init__(self, max_capacity: int = 200, num_runners: int = 10):
self.max_capacity = max_capacity
self._active_workers: OrderedDict[str, BaseIndividual] = OrderedDict()
self.status = "running"
self.task_queue = None
self.results_futures = {}
self.runners = []
self.num_runners = num_runners
self.logger = get_logger('worker_cluster')
async def start(self):
"""执行与 start 相关的核心业务流转操作。
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 """
if self.task_queue is None:
self.task_queue = Queue()
self.runners = [asyncio.create_task(self._runner(i)) for i in range(self.num_runners)]
self.logger.info(f"WorkerCluster 已启动 {self.num_runners} 个 runner 协程。")
async def _recruit_worker(self, agent_id: str) -> BaseIndividual:
"""内部方法:招聘/唤醒一个具体的 Agent 对象"""
if agent_id in self._active_workers:
self._active_workers.move_to_end(agent_id)
return self._active_workers[agent_id]
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
agent_config = await global_state_machine.get_individual.remote( agent_id)
if not agent_config:
raise ValueError(f"无法唤醒 Agent {agent_id}:数据库中不存在该档案")
worker_type = agent_config.get("type", "ordinary")
if worker_type == "skill":
worker = SkillIndividual(agent_config)
elif worker_type == "special":
worker = SpecialIndividual(agent_config)
else:
worker = OrdinaryIndividual(agent_config)
self._active_workers[agent_id] = worker
if len(self._active_workers) > self.max_capacity:
evicted_id, _ = self._active_workers.popitem(last=False)
self.logger.info(f"[WorkerCluster] 内存池满,休眠老化 Agent: {evicted_id}")
return worker
async def _runner(self, runner_id: int):
"""执行与 runner 相关的核心业务流转操作。
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
Args: runner_id (int): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 runner 实例。 """
while True:
try:
if self.task_queue is None:
await asyncio.sleep(0.1)
continue
task = await self.task_queue.get_async()
task_id = task.get("task_id")
agent_id = task.get("agent_id")
task_event = task.get("task_event")
self.logger.debug(f"[WorkerCluster Runner {runner_id}] 开始处理任务 {task_id} 给 Agent {agent_id}")
start_time = time.time()
try:
worker = await self._recruit_worker(agent_id)
result = await worker.run(task_event)
cost_time = time.time() - start_time
response = {
"success": True,
"agent_id": agent_id,
"data": result,
"metrics": {"cost_time_sec": round(cost_time, 2)}
}
except Exception as e:
self.logger.exception(f"[WorkerCluster Runner {runner_id}] 执行任务 {task_id} 时发生错误: {e}")
response = {
"success": False,
"agent_id": agent_id,
"error": str(e)
}
if task_id in self.results_futures:
future = self.results_futures[task_id]
if not future.done():
future.set_result(response)
except Exception as e:
self.logger.error(f"[WorkerCluster Runner {runner_id}] 循环发生异常: {e}")
await asyncio.sleep(1)
async def submit_task(self, task_id: str, agent_id: str, task_event: dict):
"""执行与 submit task 相关的核心业务流转操作。
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
Args: task_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 task 实例。 agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """
if not self.runners:
await self.start()
future = asyncio.Future()
self.results_futures[task_id] = future
task = {
"task_id": task_id,
"agent_id": agent_id,
"task_event": task_event
}
await self.task_queue.put_async(task)
self.logger.debug(f"[WorkerCluster] 任务 {task_id} 已加入队列。")
try:
result = await future
return result
finally:
self.results_futures.pop(task_id, None)
def get_cluster_metrics(self):
"""检索并获取特定的 cluster metrics 数据集合或实例对象。
根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """
return {
"active_worker_count": len(self._active_workers),
"max_capacity": self.max_capacity,
"cached_agent_ids": list(self._active_workers.keys()),
"queue_size": self.task_queue.size()
}