chore: initial commit for Pretor v0.1.0-alpha
正式发布 Pretor 平台的首个 alpha 版本。本项目旨在构建一个基于分布式架构的多智能体协同工作流水线。 核心功能实现: 1. 建立基于 BaseIndividual 的动态插件加载机制。 2. 实现三类核心 worker_individual 子个体。 3. 集成 Ray 框架支持分布式集群调度。 4. 基于 PostgreSQL 的全量持久化存储方案。 5. 提供完整的 FastAPI 后端与 React 前端交互界面。
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# 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.
|
||||
|
||||
from typing import List, Optional, Union, Literal, Dict, Any
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
from pretor.utils.logger import get_logger
|
||||
logger = get_logger('workflow')
|
||||
NodeType = Literal[
|
||||
"consciousness_node", "control_node", "supervisory_node", "skill_individual"
|
||||
]
|
||||
|
||||
class EventInfo(BaseModel):
|
||||
platform: str
|
||||
user_name: str
|
||||
|
||||
class LogicGate(BaseModel):
|
||||
if_fail: str = Field(..., description="失败跳转目标,如 'jump_to_step_1'")
|
||||
if_pass: Literal["continue", "exit"] = Field(default="continue", description="成功后的动作")
|
||||
|
||||
class WorkStep(BaseModel):
|
||||
step: int = Field(..., gt=0, description="步骤序号,严格自增")
|
||||
name: str = Field(..., description="步骤名称")
|
||||
node: NodeType = Field(..., description="负责执行的节点类型")
|
||||
action: str = Field(..., description="执行的原子动作")
|
||||
desc: str = Field(..., description="动作细节的自然语言描述,包含人工规范指导")
|
||||
inputs: Optional[Union[str, List[str]]] = Field(default=None, description="前置依赖输出")
|
||||
outputs: Optional[str] = Field(default=None, description="当前步骤产出物变量名")
|
||||
agent_id: Optional[str] = Field(default=None, description="分配给 skill_individual 的 Skill Individual 真实 agent_id,不可用名称代替")
|
||||
logic_gate: Optional[LogicGate] = Field(default=None, description="逻辑跳转控制")
|
||||
status: Literal["waiting", "running", "completed", "failed"] = Field(
|
||||
default="waiting",
|
||||
description="执行状态 (LLM建议保留默认值)"
|
||||
)
|
||||
|
||||
|
||||
class WorkflowStatus(BaseModel):
|
||||
step: int = Field(default=1, gt=0, description="当前运行到的工作流步数")
|
||||
status: Literal["waiting_llm_working", "waiting_tool_working", "llm_working", "tool_working"] = Field(
|
||||
default="waiting_llm_working",
|
||||
description="当前系统调度状态"
|
||||
)
|
||||
|
||||
class PretorWorkflow(BaseModel):
|
||||
title: str = Field(..., description="工作流的标题")
|
||||
work_link: List[WorkStep] = Field(..., description="工作链逻辑定义")
|
||||
# ---------------- 以下为系统级管控字段,LLM 无需关心 ---------------- #
|
||||
trace_id: str | None = Field(description="系统自动生成的追溯ID")
|
||||
version: str = Field(default="v1.0", description="系统协议版本号")
|
||||
command: Optional[str] = Field(default=None, description="触发此工作流的原始命令")
|
||||
output: Dict[str, Any] = Field(default_factory=dict, description="工作流最终产出结果")
|
||||
status: WorkflowStatus = Field(default_factory=WorkflowStatus, description="运行时状态对象")
|
||||
event_info: EventInfo | None = Field(default=None)
|
||||
context_memory: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
@model_validator(mode='after')
|
||||
def validate_workflow_integrity(self) -> 'PretorWorkflow':
|
||||
steps = [s.step for s in self.work_link]
|
||||
expected = list(range(1, len(steps) + 1))
|
||||
if steps != expected:
|
||||
raise ValueError(f"工作链步数不连续!期望 {expected},实际 {steps}")
|
||||
|
||||
max_step = len(steps)
|
||||
for s in self.work_link:
|
||||
if s.logic_gate and "jump_to_step_" in s.logic_gate.if_fail:
|
||||
try:
|
||||
target = int(s.logic_gate.if_fail.split("_")[-1])
|
||||
if target > max_step or target < 1:
|
||||
raise ValueError(f"Step {s.step} 的跳转目标 Step {target} 越界了!")
|
||||
except ValueError as e:
|
||||
if "越界" in str(e):
|
||||
raise e
|
||||
raise ValueError(f"LogicGate 格式错误: {s.logic_gate.if_fail}")
|
||||
return self
|
||||
@@ -0,0 +1,23 @@
|
||||
# workflow文档
|
||||
---
|
||||
- workflow(工作流)是作为pretor中运行任务的基本单位,workflow_manager管理整个workflow模块,包括生成workflow_template(工作流模板),生成workflow对象,和保存整个workflow_template表。
|
||||
- workflow_template是一个工作流模板,旨在由专业人士教导LLM如何编写工作流并进行任务,每个workflow_template都应该保存在 **pretor/workflow_pugin/** 文件夹下,保存格式为~_workflow_template.json,json格式为:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "",
|
||||
"desc": "",
|
||||
"work_link": [
|
||||
{
|
||||
"step": "",
|
||||
"node": "",
|
||||
"action": "",
|
||||
"desc": "",
|
||||
"input": [],
|
||||
"output": [],
|
||||
"logic_gate": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- workflow_template将由监管节点挑选交给意识节点,意识节点按照参考模板生成标准的workflow对象,转交给pipeline开始执行任务链。
|
||||
@@ -0,0 +1,364 @@
|
||||
# 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.
|
||||
|
||||
from pretor.utils.ray_hook import ray_actor_hook
|
||||
import ray
|
||||
import asyncio
|
||||
from pretor.core.workflow.workflow import PretorWorkflow, WorkStep, EventInfo
|
||||
from typing import Optional, Dict, Union, Any, List
|
||||
from pretor.utils.error import WorkflowError, WorkflowExit
|
||||
from pretor.api.platform.event import PretorEvent
|
||||
from pretor.core.individual.control_node.template import ForWorkflowInput as ControlForWorkflowInput, \
|
||||
ForWorkflow as ControlForWorkflow
|
||||
from pretor.core.individual.consciousness_node.template import (
|
||||
ForWorkflowInput as ConsciousnessForWorkflowInput,
|
||||
ForSupervisoryInput,
|
||||
ForSupervisoryNode,
|
||||
ForWorkflow as ConsciousnessForWorkflow,
|
||||
ForWorkflowEngineInput,
|
||||
ForWorkflowEngine
|
||||
)
|
||||
from pretor.core.individual.supervisory_node.template import TerminationMessage
|
||||
import pathlib
|
||||
|
||||
|
||||
def get_workflow_template(workflow_name: str) -> str:
|
||||
workflow_template = pathlib.Path(__file__).parent.parent.parent / "workflow_template" / (workflow_name + "_workflow_template.json")
|
||||
with open(workflow_template, "r", encoding="utf-8") as workflow_template_file:
|
||||
workflow_template = workflow_template_file.read()
|
||||
return workflow_template
|
||||
|
||||
|
||||
class WorkflowEngine:
|
||||
def __init__(self,
|
||||
workflow: PretorWorkflow,
|
||||
consciousness_node=None,
|
||||
control_node=None,
|
||||
supervisory_node=None):
|
||||
from pretor.utils.logger import get_logger
|
||||
self.logger = get_logger('workflow_runner')
|
||||
self.workflow: PretorWorkflow = workflow
|
||||
"""工作流:当前WorkflowEngine待执行的workflow"""
|
||||
self._steps_by_id: Dict[int, WorkStep] = {step.step: step for step in self.workflow.work_link}
|
||||
"""步骤表:将当前workflow的步骤序号和步骤内容存放"""
|
||||
self.consciousness_node = consciousness_node
|
||||
"""意识节点"""
|
||||
self.control_node = control_node
|
||||
"""控制节点"""
|
||||
self.supervisory_node = supervisory_node
|
||||
"""监督节点"""
|
||||
self._gsm = ray_actor_hook("global_state_machine").global_state_machine
|
||||
|
||||
async def _push_sse(self, msg: str) -> None:
|
||||
try:
|
||||
await self._gsm.put_pending.remote(self.workflow.trace_id, msg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _prepare_inputs(self, inputs: Optional[Union[str, List[str]]]) -> Any:
|
||||
"""
|
||||
准备输入的方法
|
||||
Args:
|
||||
inputs: 待输入的名称
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
match inputs:
|
||||
case None:
|
||||
return None
|
||||
case str(name):
|
||||
return self.workflow.context_memory.get(name)
|
||||
case list(names):
|
||||
return {k: self.workflow.context_memory.get(k) for k in names}
|
||||
|
||||
async def run(self):
|
||||
"""
|
||||
run方法
|
||||
处理并执行workflow的方法
|
||||
|
||||
"""
|
||||
self.logger.info(f"🚀 工作流引擎启动: {self.workflow.title} [Trace ID: {self.workflow.trace_id}]")
|
||||
await self._push_sse(f"[工作流启动] {self.workflow.title}")
|
||||
max_step = len(self.workflow.work_link)
|
||||
while 1 <= self.workflow.status.step <= max_step:
|
||||
current_step_id = self.workflow.status.step
|
||||
current_step = self._steps_by_id.get(current_step_id)
|
||||
if not current_step:
|
||||
self.logger.error(f"严重错误:找不到步骤 {current_step_id},工作流强制终止。")
|
||||
self.workflow.status.status = "failed"
|
||||
await self._push_sse(f"[工作流失败] 找不到步骤 {current_step_id}")
|
||||
break
|
||||
self.logger.info(f"▶️ 开始执行 Step {current_step_id}: [{current_step.node}] -> {current_step.action}")
|
||||
current_step.status = "running"
|
||||
await self._push_sse(f"[Step {current_step_id}] {current_step.name}: {current_step.desc}")
|
||||
try:
|
||||
step_input_data = self._prepare_inputs(current_step.inputs)
|
||||
step_result, is_success = await self._dispatch_to_node(current_step, step_input_data)
|
||||
if is_success:
|
||||
if current_step.outputs:
|
||||
self.workflow.context_memory[current_step.outputs] = step_result
|
||||
self.logger.debug(f"Step {current_step_id} 产出已保存至变量: '{current_step.outputs}'")
|
||||
current_step.status = "completed"
|
||||
await self._push_sse(f"[Step {current_step_id} 完成] {current_step.name}")
|
||||
else:
|
||||
self.logger.warning(f"Step {current_step_id} 执行遇到业务失败/驳回。")
|
||||
current_step.status = "failed"
|
||||
await self._push_sse(f"[Step {current_step_id} 失败] {current_step.name}")
|
||||
self._handle_logic_gate(current_step, is_success)
|
||||
except WorkflowExit:
|
||||
self.logger.info("命中 if_pass='exit',工作流被主动要求结束。")
|
||||
await self._push_sse("[工作流结束] 主动退出")
|
||||
break
|
||||
except WorkflowError as e:
|
||||
self.logger.error(f"{e},终止工作流。")
|
||||
self.workflow.status.status = "failed"
|
||||
await self._push_sse(f"[工作流失败] {e}")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Step {current_step_id} 发生系统级未捕获异常: {e}", exc_info=True)
|
||||
current_step.status = "failed"
|
||||
self.workflow.status.status = "failed"
|
||||
await self._push_sse(f"[工作流异常] {e}")
|
||||
break
|
||||
self.logger.info(f"✅ 工作流 {self.workflow.title} 执行步骤结束。")
|
||||
self.workflow.output = self.workflow.context_memory
|
||||
await self._push_sse(f"[工作流完成] {self.workflow.title}")
|
||||
await self._report_results()
|
||||
|
||||
async def _report_results(self):
|
||||
"""
|
||||
结果汇报函数
|
||||
在工作流结束后执行
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if self.workflow.status.status == "failed":
|
||||
self.logger.warning("工作流执行失败,跳过正常汇报流程。")
|
||||
return
|
||||
try:
|
||||
self.logger.info("开始生成工作流结束技术报告...")
|
||||
report = ""
|
||||
if self.consciousness_node:
|
||||
supervisory_input = ForSupervisoryInput(
|
||||
workflow=self.workflow,
|
||||
original_command=self.workflow.command or "未知命令"
|
||||
)
|
||||
report_obj = await self.consciousness_node.working.remote(supervisory_input)
|
||||
if isinstance(report_obj, ForSupervisoryNode):
|
||||
report = report_obj.output
|
||||
elif isinstance(report_obj, str):
|
||||
report = report_obj
|
||||
self.logger.debug(f"生成的报告摘要: {report[:100]}...")
|
||||
else:
|
||||
self.logger.warning("未提供 consciousness_node 句柄,跳过报告生成。")
|
||||
|
||||
if self.supervisory_node:
|
||||
term_msg = TerminationMessage(
|
||||
platform=self.workflow.event_info.platform,
|
||||
user_name=self.workflow.event_info.user_name,
|
||||
message=f"工作流执行完毕。系统报告:{report}"
|
||||
)
|
||||
user_response = await self.supervisory_node.working.remote(term_msg)
|
||||
self.workflow.context_memory["_final_user_response"] = user_response
|
||||
self.logger.info(f"Supervisory 最终回复:{user_response}")
|
||||
else:
|
||||
self.logger.warning("未提供 supervisory_node 句柄,跳过用户反馈生成。")
|
||||
except Exception:
|
||||
self.logger.exception("生成工作流执行汇报时发生错误")
|
||||
|
||||
async def _dispatch_to_node(self, step: WorkStep, input_data: Any) -> tuple[Any, bool]:
|
||||
"""
|
||||
分流器
|
||||
调用当前step的执行对象
|
||||
Args:
|
||||
step: WorkStep对象,当前需要执行的step
|
||||
input_data: 输入数据
|
||||
|
||||
Returns:
|
||||
返回llm的输出和一个bool类型的判断
|
||||
"""
|
||||
self.logger.debug(f"正在向 {step.node} 节点发送动作 {step.action}...")
|
||||
try:
|
||||
if step.node == "control_node":
|
||||
if not self.control_node:
|
||||
raise WorkflowError("未提供 control_node 句柄!")
|
||||
payload = ControlForWorkflowInput(workflow_step=step)
|
||||
# 可选:如果 input_data 需要合并,可以扩展 ControlForWorkflowInput 或将其放在 context_memory
|
||||
result_obj = await self.control_node.working.remote(payload)
|
||||
if isinstance(result_obj, ControlForWorkflow):
|
||||
return result_obj.output, True
|
||||
return result_obj, True
|
||||
|
||||
elif step.node == "consciousness_node":
|
||||
if not self.consciousness_node:
|
||||
raise WorkflowError("未提供 consciousness_node 句柄!")
|
||||
original_cmd = self.workflow.command or ""
|
||||
payload = ConsciousnessForWorkflowInput(
|
||||
workflow_step=step,
|
||||
original_command=original_cmd
|
||||
)
|
||||
result_obj = await self.consciousness_node.working.remote(payload)
|
||||
if isinstance(result_obj, ConsciousnessForWorkflow):
|
||||
return result_obj.output, True
|
||||
return result_obj, True
|
||||
|
||||
elif step.node == "skill_individual":
|
||||
self.logger.info(f"正在通过 WorkerCluster 调度 skill_individual 执行 {step.action}。")
|
||||
try:
|
||||
from pretor.utils.ray_hook import ray_actor_hook
|
||||
worker_cluster = ray_actor_hook("worker_cluster").worker_cluster
|
||||
task_id = f"{self.workflow.trace_id}_step_{step.step}"
|
||||
agent_id = step.agent_id or f"default_{step.node}"
|
||||
task_event = {
|
||||
"action": step.action,
|
||||
"description": step.desc,
|
||||
"input_data": input_data,
|
||||
"context_memory": self.workflow.context_memory
|
||||
}
|
||||
result_response = await worker_cluster.submit_task.remote(task_id, agent_id, task_event)
|
||||
|
||||
if result_response.get("success"):
|
||||
return result_response.get("data"), True
|
||||
else:
|
||||
self.logger.error(f"WorkerCluster 执行 {step.node} 失败: {result_response.get('error')}")
|
||||
return result_response.get("error"), False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.exception(f"调度 WorkerCluster 执行 {step.node} 时发生异常: {e}")
|
||||
raise WorkflowError(f"WorkerCluster 调度异常: {e}")
|
||||
else:
|
||||
raise WorkflowError(f"未知的节点类型:{step.node}")
|
||||
|
||||
except Exception:
|
||||
self.logger.exception(f"节点 {step.node} 执行动作 {step.action} 失败")
|
||||
return None, False
|
||||
|
||||
def _handle_logic_gate(self, step: WorkStep, is_success: bool):
|
||||
"""
|
||||
状态机,检测任务执行情况
|
||||
Args:
|
||||
step: WorkStep对象,当前执行的step
|
||||
is_success: bool类型,当前步骤是否成功
|
||||
|
||||
|
||||
"""
|
||||
gate = step.logic_gate
|
||||
if is_success:
|
||||
if gate and gate.if_pass == "exit":
|
||||
raise WorkflowExit()
|
||||
self.workflow.status.step += 1
|
||||
else:
|
||||
if not gate or not gate.if_fail:
|
||||
raise WorkflowError(f"步骤 {step.step} 失败且未配置 if_fail 兜底方案")
|
||||
match gate.if_fail.split("_"):
|
||||
case ["jump", "to", "step", target] if target.isdigit():
|
||||
target_step = int(target)
|
||||
self.logger.warning(f"触发逻辑门分支!从 Step {step.step} 跳转至 Step {target_step}")
|
||||
self.workflow.status.step = target_step
|
||||
case _:
|
||||
raise WorkflowError(f"未知的 if_fail 格式: {gate.if_fail}")
|
||||
|
||||
|
||||
@ray.remote
|
||||
class WorkflowRunningEngine:
|
||||
def __init__(self, consciousness_node=None, control_node=None, supervisory_node=None):
|
||||
from pretor.utils.logger import get_logger
|
||||
self.logger = get_logger('workflow_runner')
|
||||
self.runner_engine = {}
|
||||
self.workflow_queue: asyncio.Queue[PretorEvent] = None
|
||||
self.consciousness_node = consciousness_node
|
||||
self.control_node = control_node
|
||||
self.supervisory_node = supervisory_node
|
||||
self.global_state_machine = None
|
||||
|
||||
async def run(self):
|
||||
# Move actor hook to async start so we don't race during __init__ across cluster
|
||||
self.global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
|
||||
self.workflow_queue = asyncio.Queue()
|
||||
self.runner_engine = {
|
||||
f"runner_{i}": asyncio.create_task(self.runner(i))
|
||||
for i in range(10)
|
||||
}
|
||||
|
||||
async def put_event(self, event: PretorEvent) -> None:
|
||||
await self.workflow_queue.put(event)
|
||||
|
||||
async def runner(self, i: int) -> None:
|
||||
"""
|
||||
runner方法,从self.workflow_queue中不断取出任务并执行
|
||||
Args:
|
||||
i: runner序列号
|
||||
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
event = await self.workflow_queue.get()
|
||||
self.logger.info(f"WorkflowRunningEngine: runner_{i} 接收到事件 {event.trace_id} 准备生成工作流。")
|
||||
|
||||
if not self.consciousness_node:
|
||||
raise WorkflowError("未配置 consciousness_node,无法生成工作流")
|
||||
|
||||
workflow_template_name = event.context.get("workflow_template", "")
|
||||
workflow_template = get_workflow_template(workflow_template_name) if workflow_template_name else None
|
||||
|
||||
available_skills = None
|
||||
if self.global_state_machine:
|
||||
try:
|
||||
all_individuals = await self.global_state_machine.list_individuals.remote()
|
||||
available_skills = []
|
||||
for agent_id, config in all_individuals.items():
|
||||
if config.get("agent_type") == "skill_individual" or config.get("type") == "skill_individual":
|
||||
available_skills.append({
|
||||
"agent_id": agent_id,
|
||||
"name": config.get("agent_name", "Unknown"),
|
||||
"description": config.get("description", "")
|
||||
})
|
||||
except Exception as e:
|
||||
self.logger.warning(f"获取Skill Individual列表失败: {e}")
|
||||
|
||||
payload = ForWorkflowEngineInput(
|
||||
original_command=event.message,
|
||||
workflow_template=workflow_template,
|
||||
available_skills=available_skills
|
||||
)
|
||||
|
||||
result_obj = await self.consciousness_node.working.remote(payload)
|
||||
|
||||
if isinstance(result_obj, ForWorkflowEngine):
|
||||
workflow = result_obj.workflow
|
||||
|
||||
workflow.trace_id = event.trace_id
|
||||
workflow.command = event.message
|
||||
workflow.event_info = EventInfo(platform=event.platform,
|
||||
user_name=event.user_name,)
|
||||
|
||||
self.logger.info(
|
||||
f"WorkflowRunningEngine: runner_{i} 成功生成工作流 {workflow.trace_id}:{workflow.title}")
|
||||
|
||||
await self.global_state_machine.update_workflow.remote(event.trace_id, workflow)
|
||||
|
||||
workflow_engine = WorkflowEngine(workflow,
|
||||
self.consciousness_node,
|
||||
self.control_node,
|
||||
self.supervisory_node)
|
||||
await workflow_engine.run()
|
||||
else:
|
||||
self.logger.error(f"WorkflowRunningEngine: runner_{i} 无法生成工作流,返回类型为 {type(result_obj)}")
|
||||
|
||||
except asyncio.CancelledError:
|
||||
self.logger.info(f"WorkflowRunningEngine: runner_{i} 被取消。")
|
||||
raise
|
||||
except Exception as e:
|
||||
self.logger.error(f"WorkflowRunningEngine: runner_{i} 遇到未捕获的异常: {e}", exc_info=True)
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# 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.
|
||||
|
||||
from pydantic import BaseModel, model_validator
|
||||
from typing import Dict,List
|
||||
|
||||
class WorkflowTemplateStep(BaseModel):
|
||||
step: int
|
||||
node: str
|
||||
action: str
|
||||
desc: str
|
||||
input: List[str]
|
||||
output: List[str]
|
||||
logic_gate: Dict[str, str]
|
||||
|
||||
class WorkflowTemplate(BaseModel):
|
||||
name: str
|
||||
desc: str
|
||||
work_link: list[WorkflowTemplateStep]
|
||||
|
||||
@model_validator(mode='after')
|
||||
def validate_steps(self) -> 'WorkflowTemplate':
|
||||
steps = [s.step for s in self.work_link]
|
||||
if len(steps) != len(set(steps)):
|
||||
raise ValueError("Step numbers in work_link must be unique")
|
||||
return self
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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.
|
||||
|
||||
from pathlib import Path
|
||||
from pretor.core.workflow.workflow_template_generator.workflow_template import WorkflowTemplate
|
||||
|
||||
class WorkflowTemplateGenerator:
|
||||
@staticmethod
|
||||
def generate_workflow_template(workflow_template: WorkflowTemplate) -> WorkflowTemplate:
|
||||
output_dir = Path("pretor") / "workflow_template"
|
||||
if not output_dir.exists():
|
||||
output_dir.mkdir(parents=True)
|
||||
output_file = output_dir / f"{workflow_template.name}_workflow_template.json"
|
||||
with output_file.open("w", encoding="utf-8") as f:
|
||||
f.write(workflow_template.model_dump_json(indent=4))
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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 json
|
||||
from pretor.core.workflow.workflow_template_generator.workflow_template_generator import WorkflowTemplateGenerator
|
||||
from pathlib import Path
|
||||
from pretor.core.workflow.workflow_template_generator.workflow_template import WorkflowTemplate
|
||||
|
||||
from pretor.utils.logger import get_logger
|
||||
logger = get_logger('workflow_template_manager')
|
||||
|
||||
class WorkflowManager:
|
||||
def __init__(self):
|
||||
self.workflow_template_generator = WorkflowTemplateGenerator()
|
||||
self.workflow_templates_registry = {}
|
||||
self.template_path = Path("pretor/workflow_template")
|
||||
self._load_workflow_template()
|
||||
|
||||
def _load_workflow_template(self) -> None:
|
||||
for workflow_template_file in self.template_path.glob("*_workflow_template.json"):
|
||||
with workflow_template_file.open("r",encoding="utf-8") as f:
|
||||
try:
|
||||
workflow_template = json.load(f)
|
||||
self.workflow_templates_registry[workflow_template.get("name")] = workflow_template.get("desc")
|
||||
except json.decoder.JSONDecodeError:
|
||||
logger.warning(f"{workflow_template_file}不是json文件或格式错误")
|
||||
except KeyError:
|
||||
logger.warning(f"{workflow_template_file}不符合workflow_template格式")
|
||||
|
||||
def generate_workflow_template(self, workflow_template: WorkflowTemplate) -> None:
|
||||
try:
|
||||
workflow_template = self.workflow_template_generator.generate_workflow_template(workflow_template=workflow_template)
|
||||
self.workflow_templates_registry[workflow_template.name] = workflow_template.desc
|
||||
except Exception:
|
||||
logger.exception("Failed to generate workflow template")
|
||||
|
||||
def add_workflow_template(self, template_name: str, workflow_template: WorkflowTemplate) -> None:
|
||||
self.generate_workflow_template(workflow_template)
|
||||
|
||||
def get_all_workflow_templates(self) -> dict:
|
||||
return self.workflow_templates_registry
|
||||
|
||||
def delete_workflow_template(self, template_name: str) -> None:
|
||||
if template_name in self.workflow_templates_registry:
|
||||
del self.workflow_templates_registry[template_name]
|
||||
Reference in New Issue
Block a user