Skip to content

Commit 8aeaf23

Browse files
committed
feat: Refactoring workflow executor
1 parent 5a8fc56 commit 8aeaf23

16 files changed

Lines changed: 1458 additions & 0 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎虎
5+
@file: __init__.py
6+
@date:2026/6/29 16:15
7+
@desc:
8+
"""
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎虎
5+
@file: workflow.py
6+
@date:2026/6/29 10:58
7+
@desc:
8+
"""
9+
from enum import Enum
10+
from typing import List, Dict
11+
12+
from common.utils.common import group_by
13+
14+
15+
class Node:
16+
17+
def __init__(self, _id: str, _type: str, x: int, y: int, properties: dict, **kwargs):
18+
"""
19+
20+
@param _id: 节点id
21+
@param _type: 类型
22+
@param x: 节点x轴位置
23+
@param y: 节点y轴位置
24+
@param properties:
25+
@param kwargs:
26+
"""
27+
self.id = _id
28+
self.type = _type
29+
self.x = x
30+
self.y = y
31+
self.properties = properties
32+
for keyword in kwargs:
33+
self.__setattr__(keyword, kwargs.get(keyword))
34+
35+
36+
class Edge:
37+
def __init__(self, _id: str, _type: str, sourceNodeId: str, targetNodeId: str, **keywords):
38+
"""
39+
线
40+
@param _id: 线id
41+
@param _type: 线类型
42+
@param sourceNodeId:
43+
@param targetNodeId:
44+
@param keywords:
45+
"""
46+
self.id = _id
47+
self.type = _type
48+
self.sourceNodeId = sourceNodeId
49+
self.targetNodeId = targetNodeId
50+
for keyword in keywords:
51+
self.__setattr__(keyword, keywords.get(keyword))
52+
53+
54+
class EdgeNode:
55+
edge: Edge
56+
node: Node
57+
58+
def __init__(self, edge, node):
59+
self.edge = edge
60+
self.node = node
61+
62+
63+
def init_fields(workflow):
64+
result = []
65+
for node in workflow.nodes:
66+
properties = node.properties
67+
node_name = properties.get('stepName')
68+
node_id = node.id
69+
node_config = properties.get('config')
70+
result.append(NodeField(node_id, node_name, '异常信息', 'exception_message'))
71+
if node_config is not None:
72+
fields = node_config.get('fields')
73+
if fields is not None:
74+
for field in fields:
75+
result.append(NodeField(node_id, node_name, field.get('label'), field.get('value')))
76+
global_fields = node_config.get('globalFields')
77+
if global_fields is not None:
78+
for global_field in global_fields:
79+
result.append(NodeField('global', '全局变量', global_field.get('label'), global_field.get('value')))
80+
chat_fields = node_config.get('chatFields')
81+
if chat_fields is not None:
82+
for chat_field in chat_fields:
83+
result.append(NodeField('chat', 'chat', chat_field.get('label'), chat_field.get('value')))
84+
result.sort(key=lambda f: len(f.node_name + f.value), reverse=True)
85+
return result
86+
87+
88+
def get_node_parameters(node):
89+
return node.properties.get('node_data', {})
90+
91+
92+
class NodeField:
93+
def __init__(self, node_id, node_name, label, value):
94+
self.node_id = node_id
95+
self.node_name = node_name
96+
self.label = label
97+
self.value = value
98+
99+
def reset_variable(self, prompt: str):
100+
userVariable = self.node_name + "." + self.value
101+
systemVariable = f"context.get('{self.node_id}').get('{self.value}','')"
102+
return prompt.replace(userVariable, systemVariable)
103+
104+
105+
class Workflow:
106+
"""
107+
节点列表
108+
"""
109+
nodes: List[Node]
110+
"""
111+
线列表
112+
"""
113+
edges: List[Edge]
114+
"""
115+
节点id:node
116+
"""
117+
node_map: Dict[str, Node]
118+
"""
119+
节点id:当前节点id上面的所有节点
120+
"""
121+
up_node_map: Dict[str, List[EdgeNode]]
122+
"""
123+
节点id:当前节点id下面的所有节点
124+
"""
125+
next_node_map: Dict[str, List[EdgeNode]]
126+
"""
127+
节点字段
128+
"""
129+
node_field_list: List[NodeField]
130+
131+
def __init__(self, nodes: List[Node], edges: List[Edge]):
132+
self.nodes = nodes
133+
self.edges = edges
134+
self.node_map = {node.id: node for node in nodes}
135+
136+
self.up_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.sourceNodeId)) for
137+
edge in edges] for
138+
key, edges in
139+
group_by(edges, key=lambda edge: edge.targetNodeId).items()}
140+
141+
self.next_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.targetNodeId)) for edge in edges] for
142+
key, edges in
143+
group_by(edges, key=lambda edge: edge.sourceNodeId).items()}
144+
self.node_field_list = init_fields(self)
145+
146+
def get_node(self, node_id):
147+
"""
148+
根据node_id 获取节点信息
149+
@param node_id: node_id
150+
@return: 节点信息
151+
"""
152+
return self.node_map.get(node_id)
153+
154+
def get_up_edge_nodes(self, node_id) -> List[EdgeNode]:
155+
"""
156+
根据节点id 获取当前连接前置节点和连线
157+
@param node_id: 节点id
158+
@return: 节点连线列表
159+
"""
160+
return self.up_node_map.get(node_id)
161+
162+
def get_next_edge_nodes(self, node_id) -> List[EdgeNode]:
163+
"""
164+
根据节点id 获取当前连接目标节点和连线
165+
@param node_id: 节点id
166+
@return: 节点连线列表
167+
"""
168+
return self.next_node_map.get(node_id)
169+
170+
def get_up_nodes(self, node_id) -> List[Node]:
171+
"""
172+
根据节点id 获取当前连接前置节点
173+
@param node_id: 节点id
174+
@return: 节点列表
175+
"""
176+
return [en.node for en in self.up_node_map.get(node_id)]
177+
178+
def get_next_nodes(self, node_id) -> List[Node]:
179+
"""
180+
根据节点id 获取当前连接目标节点
181+
@param node_id: 节点id
182+
@return: 节点列表
183+
"""
184+
return [en.node for en in self.next_node_map.get(node_id, [])]
185+
186+
def reset_prompt(self, prompt):
187+
for node_field in self.node_field_list:
188+
prompt = node_field.reset_variable(prompt)
189+
return prompt
190+
191+
192+
class WorkflowType(Enum):
193+
# 应用
194+
APPLICATION = "APPLICATION"
195+
# 知识库
196+
KNOWLEDGE = "KNOWLEDGE"
197+
# 工具
198+
TOOL = "TOOL"
199+
200+
201+
def new_instance(flow_obj: Dict, workflow_type: WorkflowType = WorkflowType.APPLICATION):
202+
nodes = flow_obj.get('nodes')
203+
edges = flow_obj.get('edges')
204+
nodes = [Node(node.get('id'), node.get('type'), **node) for node in nodes]
205+
edges = [Edge(edge.get('id'), edge.get('type'), **edge) for edge in edges]
206+
return Workflow(nodes, edges)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎虎
5+
@file: content_type.py
6+
@date:2026/6/30 15:57
7+
@desc:
8+
"""
9+
from enum import Enum
10+
11+
12+
class ContentType(Enum):
13+
TEXT = "TEXT"
14+
REASONING = "REASONING"
15+
FAILURE = "FAILURE"
16+
TOOL = "TOOL"
17+
CONTINUE = "CONTINUE"
18+
BREAK = "BREAK"
19+
FORM = "FORM"
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎虎
5+
@file: i_node.py
6+
@date:2026/6/29 16:41
7+
@desc:
8+
"""
9+
import time
10+
from typing import Optional, Type, Callable
11+
12+
from rest_framework import serializers
13+
14+
from application.workflow.common import Node
15+
from application.workflow.message.struct.content import Content
16+
from application.workflow.status import Status
17+
18+
19+
class INode:
20+
# 当前节点支持的工作流类型
21+
supported_workflow_type_list = []
22+
# 节点类型
23+
type = None
24+
# 序列化校验器
25+
serializer_class: Optional[Type[serializers.Serializer]] = None
26+
27+
@staticmethod
28+
def is_valid(data):
29+
INode.serializer_class(data=data).is_valid(raise_exception=True)
30+
31+
def __init__(self, node, workflow_manage, get_node_parameters: Callable[[Node], dict]):
32+
self.node = node
33+
self.status = Status.BEFORE_RUNNING
34+
self.workflow_manage = workflow_manage
35+
# 节点参数
36+
self.parameters = get_node_parameters(node)
37+
# 节点运行时产生的数据
38+
self.data = {}
39+
40+
def next_success_nodes(self):
41+
edge_node_list = self.workflow_manage.workflow.get_next_edge_nodes(self.node.id)
42+
if edge_node_list is None:
43+
self.workflow_manage.next_nodes([])
44+
return
45+
self.workflow_manage.next_nodes(
46+
[en.node for en in edge_node_list if en.edge.sourceAnchorId == self.node.id + '_right'])
47+
48+
def next_fail_nodes(self):
49+
edge_node_list = self.workflow_manage.workflow.get_next_edge_nodes(self.node.id)
50+
if edge_node_list is None:
51+
self.workflow_manage.next_nodes([])
52+
return
53+
self.workflow_manage.next_nodes(
54+
[en.node for en in edge_node_list if en.edge.sourceAnchorId == self.node.id + '_exception_right'])
55+
56+
def execute(self):
57+
pass
58+
59+
def run(self):
60+
"""
61+
运行节点
62+
@return: 不响应数据
63+
"""
64+
start_time = time.time()
65+
self.status = Status.RUNNING
66+
self.data['start_time'] = start_time
67+
self._run()
68+
self.data['run_time'] = time.time() - start_time
69+
70+
def _run(self):
71+
"""
72+
执行节点
73+
@return:
74+
"""
75+
return self.execute()
76+
77+
def get_node_id(self):
78+
"""
79+
获取节点id
80+
@return: 节点id
81+
"""
82+
return self.node.id
83+
84+
def get_node_name(self):
85+
"""
86+
获取节点名称
87+
@return: 节点名称
88+
"""
89+
return self.node.properties.get("stepName")
90+
91+
def write_context(self, key, value, append=False):
92+
"""
93+
将数据写入节点上下文
94+
@param key: 数据key
95+
@param value: 数据value
96+
@param append: 是否追加
97+
@return: None
98+
"""
99+
self.workflow_manage.write_context(self.node.id, key, value, append)
100+
101+
def get_context(self, key):
102+
"""
103+
获取上下文数据 根据key
104+
@param key: key
105+
@return: 数据
106+
"""
107+
return self.workflow_manage.get_context(self.node.id, key)
108+
109+
def get_workflow_type(self):
110+
"""
111+
获取工作流类型
112+
@return: 工作流类型
113+
"""
114+
return self.workflow_manage.workflow_type
115+
116+
def get_workflow_parameters(self):
117+
"""
118+
获取工作流body
119+
@return: 工作流body数据
120+
"""
121+
return self.workflow_manage.get_parameters()
122+
123+
def get_parameters(self):
124+
"""
125+
获取节点参数数据
126+
@return: 节点参数数据
127+
"""
128+
return self.parameters
129+
130+
def get_next_nodes(self, wf):
131+
"""
132+
获取下n个基点
133+
@param wf: 工作流对象
134+
@return: 下n个节点
135+
"""
136+
return wf.get_next_nodes(self.get_node_id())
137+
138+
def write(self, message: Content):
139+
self.workflow_manage.write(message)

0 commit comments

Comments
 (0)