vectorvein 0.2.64__tar.gz → 0.2.65__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vectorvein-0.2.64 → vectorvein-0.2.65}/PKG-INFO +1 -1
- {vectorvein-0.2.64 → vectorvein-0.2.65}/pyproject.toml +1 -1
- vectorvein-0.2.65/src/vectorvein/workflow/graph/workflow.py +339 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/utils/json_to_code.py +2 -0
- vectorvein-0.2.64/src/vectorvein/workflow/graph/workflow.py +0 -170
- {vectorvein-0.2.64 → vectorvein-0.2.65}/README.md +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/__init__.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/api/__init__.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/api/client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/api/exceptions.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/api/models.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/__init__.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/anthropic_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/baichuan_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/base_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/deepseek_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/ernie_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/gemini_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/groq_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/local_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/minimax_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/mistral_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/moonshot_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/openai_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/openai_compatible_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/py.typed +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/qwen_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/stepfun_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/utils.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/xai_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/yi_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/zhipuai_client.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/py.typed +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/server/token_server.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/settings/__init__.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/settings/py.typed +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/types/__init__.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/types/defaults.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/types/enums.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/types/exception.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/types/llm_parameters.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/types/py.typed +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/types/settings.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/utilities/media_processing.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/utilities/rate_limiter.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/utilities/retry.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/graph/edge.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/graph/node.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/graph/port.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/__init__.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/audio_generation.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/control_flows.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/file_processing.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/image_generation.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/llms.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/media_editing.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/media_processing.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/output.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/relational_db.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/text_processing.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/tools.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/triggers.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/vector_db.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/video_generation.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/nodes/web_crawlers.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/utils/check.py +0 -0
- {vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/workflow/utils/layout.py +0 -0
@@ -0,0 +1,339 @@
|
|
1
|
+
import json
|
2
|
+
from typing import List, Union, Dict, Any, Optional
|
3
|
+
|
4
|
+
from .node import Node
|
5
|
+
from .edge import Edge
|
6
|
+
from .port import InputPort, OutputPort
|
7
|
+
from ..utils.layout import layout
|
8
|
+
from ..utils.check import (
|
9
|
+
WorkflowCheckResult,
|
10
|
+
check_dag,
|
11
|
+
check_ui,
|
12
|
+
check_useless_nodes,
|
13
|
+
check_required_ports,
|
14
|
+
check_override_ports,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
class Workflow:
|
19
|
+
def __init__(self) -> None:
|
20
|
+
self.nodes: List[Node] = []
|
21
|
+
self.edges: List[Edge] = []
|
22
|
+
|
23
|
+
def add_node(self, node: Node):
|
24
|
+
self.nodes.append(node)
|
25
|
+
|
26
|
+
def add_nodes(self, nodes: List[Node]):
|
27
|
+
self.nodes.extend(nodes)
|
28
|
+
|
29
|
+
def add_edge(self, edge: Edge):
|
30
|
+
self.edges.append(edge)
|
31
|
+
|
32
|
+
def connect(
|
33
|
+
self,
|
34
|
+
source_node: Union[str, Node],
|
35
|
+
source_port: str,
|
36
|
+
target_node: Union[str, Node],
|
37
|
+
target_port: str,
|
38
|
+
):
|
39
|
+
# 获取源节点ID
|
40
|
+
if isinstance(source_node, Node):
|
41
|
+
source_node_id = source_node.id
|
42
|
+
else:
|
43
|
+
source_node_id = source_node
|
44
|
+
|
45
|
+
# 获取目标节点ID
|
46
|
+
if isinstance(target_node, Node):
|
47
|
+
target_node_id = target_node.id
|
48
|
+
else:
|
49
|
+
target_node_id = target_node
|
50
|
+
|
51
|
+
# 检查源节点是否存在
|
52
|
+
source_node_exists = any(node.id == source_node_id for node in self.nodes)
|
53
|
+
if not source_node_exists:
|
54
|
+
raise ValueError(f"Source node not found: {source_node_id}")
|
55
|
+
|
56
|
+
# 检查目标节点是否存在
|
57
|
+
target_node_exists = any(node.id == target_node_id for node in self.nodes)
|
58
|
+
if not target_node_exists:
|
59
|
+
raise ValueError(f"Target node not found: {target_node_id}")
|
60
|
+
|
61
|
+
# 检查源节点的端口是否存在
|
62
|
+
source_node_obj = next(node for node in self.nodes if node.id == source_node_id)
|
63
|
+
if not source_node_obj.has_output_port(source_port):
|
64
|
+
raise ValueError(f"Source node {source_node_id} has no output port: {source_port}")
|
65
|
+
|
66
|
+
# 检查目标节点的端口是否存在
|
67
|
+
target_node_obj = next(node for node in self.nodes if node.id == target_node_id)
|
68
|
+
if not target_node_obj.has_input_port(target_port):
|
69
|
+
raise ValueError(f"Target node {target_node_id} has no input port: {target_port}")
|
70
|
+
|
71
|
+
# 检查目标端口是否已有被连接的线
|
72
|
+
for edge in self.edges:
|
73
|
+
if edge.target == target_node_id and edge.target_handle == target_port:
|
74
|
+
raise ValueError(
|
75
|
+
f"The input port {target_port} of the target node {target_node_id} is already connected: {edge.source}({edge.source_handle}) → {edge.target}({edge.target_handle})"
|
76
|
+
)
|
77
|
+
|
78
|
+
# 创建并添加边
|
79
|
+
edge_id = f"vueflow__edge-{source_node_id}{source_port}-{target_node_id}{target_port}"
|
80
|
+
edge = Edge(edge_id, source_node_id, source_port, target_node_id, target_port)
|
81
|
+
self.add_edge(edge)
|
82
|
+
|
83
|
+
def to_dict(self):
|
84
|
+
return {
|
85
|
+
"nodes": [node.to_dict() for node in self.nodes],
|
86
|
+
"edges": [edge.to_dict() for edge in self.edges],
|
87
|
+
"viewport": {"x": 0, "y": 0, "zoom": 1},
|
88
|
+
}
|
89
|
+
|
90
|
+
def to_json(self, ensure_ascii=False):
|
91
|
+
return json.dumps(self.to_dict(), ensure_ascii=ensure_ascii)
|
92
|
+
|
93
|
+
def to_mermaid(self) -> str:
|
94
|
+
"""生成 Mermaid 格式的流程图。
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
str: Mermaid 格式的流程图文本
|
98
|
+
"""
|
99
|
+
lines = ["flowchart TD"]
|
100
|
+
|
101
|
+
# 创建节点类型到序号的映射
|
102
|
+
type_counters = {}
|
103
|
+
node_id_to_label = {}
|
104
|
+
|
105
|
+
# 首先为所有节点生成标签
|
106
|
+
for node in self.nodes:
|
107
|
+
node_type = node.type.lower()
|
108
|
+
if node_type not in type_counters:
|
109
|
+
type_counters[node_type] = 0
|
110
|
+
node_label = f"{node_type}_{type_counters[node_type]}"
|
111
|
+
node_id_to_label[node.id] = node_label
|
112
|
+
type_counters[node_type] += 1
|
113
|
+
|
114
|
+
# 添加节点定义
|
115
|
+
for node in self.nodes:
|
116
|
+
node_label = node_id_to_label[node.id]
|
117
|
+
lines.append(f' {node_label}["{node_label} ({node.type})"]')
|
118
|
+
|
119
|
+
lines.append("") # 添加一个空行分隔节点和边的定义
|
120
|
+
|
121
|
+
# 添加边的定义
|
122
|
+
for edge in self.edges:
|
123
|
+
source_label = node_id_to_label[edge.source]
|
124
|
+
target_label = node_id_to_label[edge.target]
|
125
|
+
label = f"{edge.source_handle} → {edge.target_handle}"
|
126
|
+
lines.append(f" {source_label} -->|{label}| {target_label}")
|
127
|
+
|
128
|
+
return "\n".join(lines)
|
129
|
+
|
130
|
+
def check(self) -> WorkflowCheckResult:
|
131
|
+
"""检查流程图的有效性。
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
WorkflowCheckResult: 包含各种检查结果的字典
|
135
|
+
"""
|
136
|
+
dag_check = check_dag(self) # 检查流程图是否为有向无环图,并检测是否存在孤立节点。
|
137
|
+
ui_check = check_ui(self)
|
138
|
+
useless_nodes = check_useless_nodes(self)
|
139
|
+
required_ports = check_required_ports(self)
|
140
|
+
override_ports = check_override_ports(self)
|
141
|
+
|
142
|
+
# 合并结果
|
143
|
+
result: WorkflowCheckResult = {
|
144
|
+
"no_cycle": dag_check["no_cycle"],
|
145
|
+
"no_isolated_nodes": dag_check["no_isolated_nodes"],
|
146
|
+
"ui_warnings": ui_check,
|
147
|
+
"useless_nodes": useless_nodes,
|
148
|
+
"required_ports": required_ports,
|
149
|
+
"override_ports": override_ports,
|
150
|
+
}
|
151
|
+
|
152
|
+
return result
|
153
|
+
|
154
|
+
def layout(self, options: Optional[Dict[str, Any]] = None) -> "Workflow":
|
155
|
+
"""对工作流中的节点进行自动布局,计算并更新每个节点的位置。
|
156
|
+
|
157
|
+
此方法实现了一个简单的分层布局算法,将节点按照有向图的拓扑结构进行排列。
|
158
|
+
|
159
|
+
Args:
|
160
|
+
options: 布局选项,包括:
|
161
|
+
- direction: 布局方向 ('TB', 'BT', 'LR', 'RL'),默认 'LR'
|
162
|
+
- node_spacing: 同一层级节点间的间距,默认 500
|
163
|
+
- layer_spacing: 不同层级间的间距,默认 400
|
164
|
+
- margin_x: 图形左右边距,默认 20
|
165
|
+
- margin_y: 图形上下边距,默认 20
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
布局后的工作流对象
|
169
|
+
"""
|
170
|
+
layout(self.nodes, self.edges, options)
|
171
|
+
return self
|
172
|
+
|
173
|
+
@classmethod
|
174
|
+
def from_json(cls, json_str: str) -> "Workflow":
|
175
|
+
"""从 JSON 字符串创建工作流对象。
|
176
|
+
|
177
|
+
Args:
|
178
|
+
json_str: JSON 字符串
|
179
|
+
|
180
|
+
Returns:
|
181
|
+
Workflow: 工作流对象
|
182
|
+
"""
|
183
|
+
workflow = cls()
|
184
|
+
data = json.loads(json_str)
|
185
|
+
|
186
|
+
# 创建节点
|
187
|
+
for node_data in data.get("nodes", []):
|
188
|
+
node_type = node_data["type"]
|
189
|
+
category = node_data["category"]
|
190
|
+
task_name = node_data["data"]["task_name"]
|
191
|
+
|
192
|
+
# 尝试动态导入节点类
|
193
|
+
NodeClass = None
|
194
|
+
try:
|
195
|
+
# 如果task_name包含分类信息
|
196
|
+
if "." in task_name:
|
197
|
+
category, _ = task_name.split(".")
|
198
|
+
module_path = f"vectorvein.workflow.nodes.{category}"
|
199
|
+
module = __import__(module_path, fromlist=[node_type])
|
200
|
+
if hasattr(module, node_type):
|
201
|
+
NodeClass = getattr(module, node_type)
|
202
|
+
except (ImportError, AttributeError):
|
203
|
+
pass
|
204
|
+
|
205
|
+
if not NodeClass:
|
206
|
+
raise ValueError(f"Node class not found: {node_type}")
|
207
|
+
# 创建节点实例以获取默认值
|
208
|
+
node_instance = NodeClass()
|
209
|
+
|
210
|
+
# 使用节点实例的基本属性
|
211
|
+
node = Node(
|
212
|
+
node_type=node_type,
|
213
|
+
category=category,
|
214
|
+
task_name=task_name,
|
215
|
+
description=node_data["data"].get(
|
216
|
+
"description", node_instance.description if hasattr(node_instance, "description") else ""
|
217
|
+
),
|
218
|
+
node_id=node_data["id"],
|
219
|
+
position=node_data.get("position", {"x": 0, "y": 0}),
|
220
|
+
seleted_workflow_title=node_data["data"].get("seleted_workflow_title", ""),
|
221
|
+
is_template=node_data["data"].get("is_template", False),
|
222
|
+
initialized=node_data.get("initialized", False),
|
223
|
+
can_add_input_ports=node_data["data"].get("has_inputs", False),
|
224
|
+
can_add_output_ports=node_data["data"].get("has_outputs", False),
|
225
|
+
)
|
226
|
+
|
227
|
+
# 处理端口
|
228
|
+
for port_name, port_data in node_data["data"].get("template", {}).items():
|
229
|
+
# 如果端口已存在于节点实例中,直接修改其属性
|
230
|
+
if port_name in node_instance.ports:
|
231
|
+
# 直接修改原始端口的属性,而不是创建新端口
|
232
|
+
port = node_instance.ports[port_name]
|
233
|
+
|
234
|
+
# 更新端口的属性
|
235
|
+
if "field_type" in port_data:
|
236
|
+
port.port_type = port_data["field_type"]
|
237
|
+
if "required" in port_data:
|
238
|
+
port.required = port_data["required"]
|
239
|
+
if "show" in port_data:
|
240
|
+
port.show = port_data["show"]
|
241
|
+
if "value" in port_data:
|
242
|
+
port.value = port_data["value"]
|
243
|
+
if "options" in port_data:
|
244
|
+
port.options = port_data["options"]
|
245
|
+
if "type" in port_data:
|
246
|
+
port.field_type = port_data["type"]
|
247
|
+
if "max_length" in port_data:
|
248
|
+
port.max_length = port_data["max_length"]
|
249
|
+
if "support_file_types" in port_data and port_data["support_file_types"]:
|
250
|
+
port.support_file_types = port_data["support_file_types"].split(", ")
|
251
|
+
if "multiple" in port_data:
|
252
|
+
port.multiple = port_data["multiple"]
|
253
|
+
if "group" in port_data:
|
254
|
+
port.group = port_data["group"]
|
255
|
+
if "group_collpased" in port_data:
|
256
|
+
port.group_collpased = port_data["group_collpased"]
|
257
|
+
if "has_tooltip" in port_data:
|
258
|
+
port.has_tooltip = port_data["has_tooltip"]
|
259
|
+
if "max" in port_data:
|
260
|
+
port.max = port_data["max"]
|
261
|
+
if "min" in port_data:
|
262
|
+
port.min = port_data["min"]
|
263
|
+
if "list" in port_data:
|
264
|
+
port.list = port_data["list"]
|
265
|
+
else:
|
266
|
+
# 对于新添加的端口,检查是否允许添加
|
267
|
+
port_type = port_data.get("field_type", "text")
|
268
|
+
is_output = port_data.get("is_output", False)
|
269
|
+
|
270
|
+
# 检查节点是否允许添加该类型的端口
|
271
|
+
if (is_output and not node.can_add_output_ports) or (
|
272
|
+
not is_output and not node.can_add_input_ports
|
273
|
+
):
|
274
|
+
# 如果不允许添加,跳过该端口
|
275
|
+
continue
|
276
|
+
|
277
|
+
# 创建并添加新端口
|
278
|
+
if is_output:
|
279
|
+
port = OutputPort(
|
280
|
+
name=port_name,
|
281
|
+
port_type=port_type,
|
282
|
+
required=port_data.get("required", False),
|
283
|
+
show=port_data.get("show", False),
|
284
|
+
value=port_data.get("value"),
|
285
|
+
options=port_data.get("options"),
|
286
|
+
field_type=port_data.get("type"),
|
287
|
+
max_length=port_data.get("max_length"),
|
288
|
+
support_file_types=port_data.get("support_file_types", "").split(", ")
|
289
|
+
if port_data.get("support_file_types")
|
290
|
+
else None,
|
291
|
+
multiple=port_data.get("multiple"),
|
292
|
+
group=port_data.get("group"),
|
293
|
+
group_collpased=port_data.get("group_collpased", False),
|
294
|
+
has_tooltip=port_data.get("has_tooltip", False),
|
295
|
+
max=port_data.get("max"),
|
296
|
+
min=port_data.get("min"),
|
297
|
+
list=port_data.get("list", False),
|
298
|
+
)
|
299
|
+
else:
|
300
|
+
port = InputPort(
|
301
|
+
name=port_name,
|
302
|
+
port_type=port_type,
|
303
|
+
required=port_data.get("required", True),
|
304
|
+
show=port_data.get("show", False),
|
305
|
+
value=port_data.get("value"),
|
306
|
+
options=port_data.get("options"),
|
307
|
+
field_type=port_data.get("type"),
|
308
|
+
max_length=port_data.get("max_length"),
|
309
|
+
support_file_types=port_data.get("support_file_types", "").split(", ")
|
310
|
+
if port_data.get("support_file_types")
|
311
|
+
else None,
|
312
|
+
multiple=port_data.get("multiple"),
|
313
|
+
group=port_data.get("group"),
|
314
|
+
group_collpased=port_data.get("group_collpased", False),
|
315
|
+
has_tooltip=port_data.get("has_tooltip", False),
|
316
|
+
max=port_data.get("max"),
|
317
|
+
min=port_data.get("min"),
|
318
|
+
list=port_data.get("list", False),
|
319
|
+
)
|
320
|
+
|
321
|
+
# 添加新端口到节点
|
322
|
+
node.ports[port_name] = port
|
323
|
+
|
324
|
+
workflow.add_node(node)
|
325
|
+
|
326
|
+
# 创建边
|
327
|
+
for edge_data in data.get("edges", []):
|
328
|
+
edge = Edge(
|
329
|
+
id=edge_data["id"],
|
330
|
+
source=edge_data["source"],
|
331
|
+
source_handle=edge_data["sourceHandle"],
|
332
|
+
target=edge_data["target"],
|
333
|
+
target_handle=edge_data["targetHandle"],
|
334
|
+
animated=edge_data.get("animated", True),
|
335
|
+
type=edge_data.get("type", "default"),
|
336
|
+
)
|
337
|
+
workflow.add_edge(edge)
|
338
|
+
|
339
|
+
return workflow
|
@@ -156,6 +156,8 @@ def generate_python_code(
|
|
156
156
|
code.append("")
|
157
157
|
for node_info in node_instances.values():
|
158
158
|
for port in node_info["add_ports"]:
|
159
|
+
if "name" not in port:
|
160
|
+
continue
|
159
161
|
params = [
|
160
162
|
f"name={to_python_str(port['name'])}",
|
161
163
|
f"port_type={to_python_str(port['field_type'])}",
|
@@ -1,170 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from typing import List, Union, Dict, Any, Optional
|
3
|
-
|
4
|
-
from .node import Node
|
5
|
-
from .edge import Edge
|
6
|
-
from ..utils.layout import layout
|
7
|
-
from ..utils.check import (
|
8
|
-
WorkflowCheckResult,
|
9
|
-
check_dag,
|
10
|
-
check_ui,
|
11
|
-
check_useless_nodes,
|
12
|
-
check_required_ports,
|
13
|
-
check_override_ports,
|
14
|
-
)
|
15
|
-
|
16
|
-
|
17
|
-
class Workflow:
|
18
|
-
def __init__(self) -> None:
|
19
|
-
self.nodes: List[Node] = []
|
20
|
-
self.edges: List[Edge] = []
|
21
|
-
|
22
|
-
def add_node(self, node: Node):
|
23
|
-
self.nodes.append(node)
|
24
|
-
|
25
|
-
def add_nodes(self, nodes: List[Node]):
|
26
|
-
self.nodes.extend(nodes)
|
27
|
-
|
28
|
-
def add_edge(self, edge: Edge):
|
29
|
-
self.edges.append(edge)
|
30
|
-
|
31
|
-
def connect(
|
32
|
-
self,
|
33
|
-
source_node: Union[str, Node],
|
34
|
-
source_port: str,
|
35
|
-
target_node: Union[str, Node],
|
36
|
-
target_port: str,
|
37
|
-
):
|
38
|
-
# 获取源节点ID
|
39
|
-
if isinstance(source_node, Node):
|
40
|
-
source_node_id = source_node.id
|
41
|
-
else:
|
42
|
-
source_node_id = source_node
|
43
|
-
|
44
|
-
# 获取目标节点ID
|
45
|
-
if isinstance(target_node, Node):
|
46
|
-
target_node_id = target_node.id
|
47
|
-
else:
|
48
|
-
target_node_id = target_node
|
49
|
-
|
50
|
-
# 检查源节点是否存在
|
51
|
-
source_node_exists = any(node.id == source_node_id for node in self.nodes)
|
52
|
-
if not source_node_exists:
|
53
|
-
raise ValueError(f"Source node not found: {source_node_id}")
|
54
|
-
|
55
|
-
# 检查目标节点是否存在
|
56
|
-
target_node_exists = any(node.id == target_node_id for node in self.nodes)
|
57
|
-
if not target_node_exists:
|
58
|
-
raise ValueError(f"Target node not found: {target_node_id}")
|
59
|
-
|
60
|
-
# 检查源节点的端口是否存在
|
61
|
-
source_node_obj = next(node for node in self.nodes if node.id == source_node_id)
|
62
|
-
if not source_node_obj.has_output_port(source_port):
|
63
|
-
raise ValueError(f"Source node {source_node_id} has no output port: {source_port}")
|
64
|
-
|
65
|
-
# 检查目标节点的端口是否存在
|
66
|
-
target_node_obj = next(node for node in self.nodes if node.id == target_node_id)
|
67
|
-
if not target_node_obj.has_input_port(target_port):
|
68
|
-
raise ValueError(f"Target node {target_node_id} has no input port: {target_port}")
|
69
|
-
|
70
|
-
# 检查目标端口是否已有被连接的线
|
71
|
-
for edge in self.edges:
|
72
|
-
if edge.target == target_node_id and edge.target_handle == target_port:
|
73
|
-
raise ValueError(
|
74
|
-
f"The input port {target_port} of the target node {target_node_id} is already connected: {edge.source}({edge.source_handle}) → {edge.target}({edge.target_handle})"
|
75
|
-
)
|
76
|
-
|
77
|
-
# 创建并添加边
|
78
|
-
edge_id = f"vueflow__edge-{source_node_id}{source_port}-{target_node_id}{target_port}"
|
79
|
-
edge = Edge(edge_id, source_node_id, source_port, target_node_id, target_port)
|
80
|
-
self.add_edge(edge)
|
81
|
-
|
82
|
-
def to_dict(self):
|
83
|
-
return {
|
84
|
-
"nodes": [node.to_dict() for node in self.nodes],
|
85
|
-
"edges": [edge.to_dict() for edge in self.edges],
|
86
|
-
"viewport": {"x": 0, "y": 0, "zoom": 1},
|
87
|
-
}
|
88
|
-
|
89
|
-
def to_json(self, ensure_ascii=False):
|
90
|
-
return json.dumps(self.to_dict(), ensure_ascii=ensure_ascii)
|
91
|
-
|
92
|
-
def to_mermaid(self) -> str:
|
93
|
-
"""生成 Mermaid 格式的流程图。
|
94
|
-
|
95
|
-
Returns:
|
96
|
-
str: Mermaid 格式的流程图文本
|
97
|
-
"""
|
98
|
-
lines = ["flowchart TD"]
|
99
|
-
|
100
|
-
# 创建节点类型到序号的映射
|
101
|
-
type_counters = {}
|
102
|
-
node_id_to_label = {}
|
103
|
-
|
104
|
-
# 首先为所有节点生成标签
|
105
|
-
for node in self.nodes:
|
106
|
-
node_type = node.type.lower()
|
107
|
-
if node_type not in type_counters:
|
108
|
-
type_counters[node_type] = 0
|
109
|
-
node_label = f"{node_type}_{type_counters[node_type]}"
|
110
|
-
node_id_to_label[node.id] = node_label
|
111
|
-
type_counters[node_type] += 1
|
112
|
-
|
113
|
-
# 添加节点定义
|
114
|
-
for node in self.nodes:
|
115
|
-
node_label = node_id_to_label[node.id]
|
116
|
-
lines.append(f' {node_label}["{node_label} ({node.type})"]')
|
117
|
-
|
118
|
-
lines.append("") # 添加一个空行分隔节点和边的定义
|
119
|
-
|
120
|
-
# 添加边的定义
|
121
|
-
for edge in self.edges:
|
122
|
-
source_label = node_id_to_label[edge.source]
|
123
|
-
target_label = node_id_to_label[edge.target]
|
124
|
-
label = f"{edge.source_handle} → {edge.target_handle}"
|
125
|
-
lines.append(f" {source_label} -->|{label}| {target_label}")
|
126
|
-
|
127
|
-
return "\n".join(lines)
|
128
|
-
|
129
|
-
def check(self) -> WorkflowCheckResult:
|
130
|
-
"""检查流程图的有效性。
|
131
|
-
|
132
|
-
Returns:
|
133
|
-
WorkflowCheckResult: 包含各种检查结果的字典
|
134
|
-
"""
|
135
|
-
dag_check = check_dag(self) # 检查流程图是否为有向无环图,并检测是否存在孤立节点。
|
136
|
-
ui_check = check_ui(self)
|
137
|
-
useless_nodes = check_useless_nodes(self)
|
138
|
-
required_ports = check_required_ports(self)
|
139
|
-
override_ports = check_override_ports(self)
|
140
|
-
|
141
|
-
# 合并结果
|
142
|
-
result: WorkflowCheckResult = {
|
143
|
-
"no_cycle": dag_check["no_cycle"],
|
144
|
-
"no_isolated_nodes": dag_check["no_isolated_nodes"],
|
145
|
-
"ui_warnings": ui_check,
|
146
|
-
"useless_nodes": useless_nodes,
|
147
|
-
"required_ports": required_ports,
|
148
|
-
"override_ports": override_ports,
|
149
|
-
}
|
150
|
-
|
151
|
-
return result
|
152
|
-
|
153
|
-
def layout(self, options: Optional[Dict[str, Any]] = None) -> "Workflow":
|
154
|
-
"""对工作流中的节点进行自动布局,计算并更新每个节点的位置。
|
155
|
-
|
156
|
-
此方法实现了一个简单的分层布局算法,将节点按照有向图的拓扑结构进行排列。
|
157
|
-
|
158
|
-
Args:
|
159
|
-
options: 布局选项,包括:
|
160
|
-
- direction: 布局方向 ('TB', 'BT', 'LR', 'RL'),默认 'LR'
|
161
|
-
- node_spacing: 同一层级节点间的间距,默认 500
|
162
|
-
- layer_spacing: 不同层级间的间距,默认 400
|
163
|
-
- margin_x: 图形左右边距,默认 20
|
164
|
-
- margin_y: 图形上下边距,默认 20
|
165
|
-
|
166
|
-
Returns:
|
167
|
-
布局后的工作流对象
|
168
|
-
"""
|
169
|
-
layout(self.nodes, self.edges, options)
|
170
|
-
return self
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{vectorvein-0.2.64 → vectorvein-0.2.65}/src/vectorvein/chat_clients/openai_compatible_client.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|