vectorvein 0.2.71__tar.gz → 0.2.73__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.
Files changed (68) hide show
  1. {vectorvein-0.2.71 → vectorvein-0.2.73}/PKG-INFO +1 -1
  2. {vectorvein-0.2.71 → vectorvein-0.2.73}/pyproject.toml +1 -1
  3. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/graph/workflow.py +3 -0
  4. vectorvein-0.2.73/src/vectorvein/workflow/utils/analyse.py +422 -0
  5. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/utils/check.py +28 -0
  6. vectorvein-0.2.71/src/vectorvein/workflow/utils/analyse.py +0 -115
  7. {vectorvein-0.2.71 → vectorvein-0.2.73}/README.md +0 -0
  8. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/__init__.py +0 -0
  9. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/api/__init__.py +0 -0
  10. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/api/client.py +0 -0
  11. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/api/exceptions.py +0 -0
  12. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/api/models.py +0 -0
  13. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/__init__.py +0 -0
  14. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/anthropic_client.py +0 -0
  15. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/baichuan_client.py +0 -0
  16. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/base_client.py +0 -0
  17. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/deepseek_client.py +0 -0
  18. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/ernie_client.py +0 -0
  19. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/gemini_client.py +0 -0
  20. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/groq_client.py +0 -0
  21. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/local_client.py +0 -0
  22. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/minimax_client.py +0 -0
  23. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/mistral_client.py +0 -0
  24. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/moonshot_client.py +0 -0
  25. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/openai_client.py +0 -0
  26. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/openai_compatible_client.py +0 -0
  27. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/py.typed +0 -0
  28. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/qwen_client.py +0 -0
  29. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/stepfun_client.py +0 -0
  30. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/utils.py +0 -0
  31. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/xai_client.py +0 -0
  32. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/yi_client.py +0 -0
  33. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/zhipuai_client.py +0 -0
  34. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/py.typed +0 -0
  35. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/server/token_server.py +0 -0
  36. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/settings/__init__.py +0 -0
  37. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/settings/py.typed +0 -0
  38. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/__init__.py +0 -0
  39. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/defaults.py +0 -0
  40. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/enums.py +0 -0
  41. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/exception.py +0 -0
  42. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/llm_parameters.py +0 -0
  43. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/py.typed +0 -0
  44. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/settings.py +0 -0
  45. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/utilities/media_processing.py +0 -0
  46. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/utilities/rate_limiter.py +0 -0
  47. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/utilities/retry.py +0 -0
  48. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/graph/edge.py +0 -0
  49. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/graph/node.py +0 -0
  50. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/graph/port.py +0 -0
  51. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/__init__.py +0 -0
  52. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/audio_generation.py +0 -0
  53. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/control_flows.py +0 -0
  54. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/file_processing.py +0 -0
  55. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/image_generation.py +0 -0
  56. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/llms.py +0 -0
  57. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/media_editing.py +0 -0
  58. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/media_processing.py +0 -0
  59. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/output.py +0 -0
  60. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/relational_db.py +0 -0
  61. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/text_processing.py +0 -0
  62. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/tools.py +0 -0
  63. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/triggers.py +0 -0
  64. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/vector_db.py +0 -0
  65. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/video_generation.py +0 -0
  66. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/web_crawlers.py +0 -0
  67. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/utils/json_to_code.py +0 -0
  68. {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/utils/layout.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vectorvein
3
- Version: 0.2.71
3
+ Version: 0.2.73
4
4
  Summary: VectorVein Python SDK
5
5
  Author-Email: Anderson <andersonby@163.com>
6
6
  License: MIT
@@ -17,7 +17,7 @@ description = "VectorVein Python SDK"
17
17
  name = "vectorvein"
18
18
  readme = "README.md"
19
19
  requires-python = ">=3.10"
20
- version = "0.2.71"
20
+ version = "0.2.73"
21
21
 
22
22
  [project.license]
23
23
  text = "MIT"
@@ -12,6 +12,7 @@ from ..utils.check import (
12
12
  check_useless_nodes,
13
13
  check_required_ports,
14
14
  check_override_ports,
15
+ check_output_nodes_with_no_inputs,
15
16
  )
16
17
 
17
18
 
@@ -138,6 +139,7 @@ class Workflow:
138
139
  useless_nodes = check_useless_nodes(self)
139
140
  required_ports = check_required_ports(self)
140
141
  override_ports = check_override_ports(self)
142
+ output_nodes_with_no_inputs = check_output_nodes_with_no_inputs(self)
141
143
 
142
144
  # 合并结果
143
145
  result: WorkflowCheckResult = {
@@ -147,6 +149,7 @@ class Workflow:
147
149
  "useless_nodes": useless_nodes,
148
150
  "required_ports": required_ports,
149
151
  "override_ports": override_ports,
152
+ "output_nodes_with_no_inputs": output_nodes_with_no_inputs,
150
153
  }
151
154
 
152
155
  return result
@@ -0,0 +1,422 @@
1
+ import json
2
+ from typing import TypedDict, Any, overload
3
+
4
+
5
+ class PortRecord(TypedDict):
6
+ """
7
+ 端口记录
8
+ """
9
+
10
+ name: str
11
+ type: str
12
+ show: bool
13
+ value: Any
14
+ connected: bool
15
+
16
+
17
+ class NodeRecord(TypedDict):
18
+ """
19
+ 节点记录
20
+ """
21
+
22
+ id: str
23
+ name: str
24
+ type: str
25
+ category: str
26
+ ports: list[PortRecord]
27
+
28
+
29
+ class AnalyseResult(TypedDict):
30
+ """
31
+ 分析结果
32
+ """
33
+
34
+ nodes: list[NodeRecord]
35
+
36
+
37
+ def analyse_workflow_record(
38
+ json_str: str, connected_only: bool = False, reserver_programming_function_ports: bool = False
39
+ ) -> AnalyseResult:
40
+ """
41
+ 分析工作流JSON字符串,提取节点和端口信息
42
+
43
+ Args:
44
+ json_str: 工作流JSON字符串
45
+
46
+ Returns:
47
+ 分析结果
48
+ """
49
+ # 解析JSON
50
+ workflow_data = json.loads(json_str)
51
+
52
+ # 收集所有连接的端口
53
+ connected_ports = set()
54
+ for edge in workflow_data["edges"]:
55
+ source_id = edge["source"]
56
+ target_id = edge["target"]
57
+ source_handle = edge["sourceHandle"]
58
+ target_handle = edge["targetHandle"]
59
+ connected_ports.add((source_id, source_handle))
60
+ connected_ports.add((target_id, target_handle))
61
+
62
+ # 分析节点
63
+ nodes_records = []
64
+
65
+ for node in workflow_data["nodes"]:
66
+ node_id = node["id"]
67
+ node_type = node["type"]
68
+ category = node["category"]
69
+
70
+ # 跳过辅助节点
71
+ if category == "assistedNodes":
72
+ continue
73
+
74
+ # 获取任务名称
75
+ task_name = node["data"]["task_name"].split(".")[-1] if "task_name" in node["data"] else ""
76
+
77
+ # 收集端口信息
78
+ ports_records = []
79
+
80
+ if "template" in node["data"]:
81
+ for port in node["data"]["template"].values():
82
+ if "name" not in port:
83
+ continue
84
+
85
+ port_is_connected = (node_id, port["name"]) in connected_ports
86
+
87
+ if node_type != "ProgrammingFunction" or not reserver_programming_function_ports:
88
+ if connected_only and not port_is_connected:
89
+ continue
90
+
91
+ port_record: PortRecord = {
92
+ "name": port["name"],
93
+ "type": port.get("field_type", port.get("type", "")),
94
+ "show": port.get("show", False),
95
+ "value": port.get("value", None),
96
+ "connected": port_is_connected,
97
+ }
98
+
99
+ ports_records.append(port_record)
100
+
101
+ # 创建节点记录
102
+ node_record: NodeRecord = {
103
+ "id": node_id,
104
+ "name": task_name,
105
+ "type": node_type,
106
+ "category": category,
107
+ "ports": ports_records,
108
+ }
109
+
110
+ nodes_records.append(node_record)
111
+
112
+ # 返回分析结果
113
+ result: AnalyseResult = {"nodes": nodes_records}
114
+
115
+ return result
116
+
117
+
118
+ @overload
119
+ def prettify_value(
120
+ value: str,
121
+ max_length: int,
122
+ preserve_escapes: bool = True,
123
+ only_control_chars: bool = True,
124
+ ) -> str: ...
125
+
126
+
127
+ @overload
128
+ def prettify_value(
129
+ value: list,
130
+ max_length: int,
131
+ preserve_escapes: bool = True,
132
+ only_control_chars: bool = True,
133
+ ) -> list: ...
134
+
135
+
136
+ @overload
137
+ def prettify_value(
138
+ value: dict,
139
+ max_length: int,
140
+ preserve_escapes: bool = True,
141
+ only_control_chars: bool = True,
142
+ ) -> dict: ...
143
+
144
+
145
+ @overload
146
+ def prettify_value(
147
+ value: Any,
148
+ max_length: int,
149
+ preserve_escapes: bool = True,
150
+ only_control_chars: bool = True,
151
+ ) -> Any: ...
152
+
153
+
154
+ def prettify_value(
155
+ value: Any,
156
+ max_length: int,
157
+ preserve_escapes: bool = False,
158
+ only_control_chars: bool = True,
159
+ ) -> Any:
160
+ """
161
+ 截断字符串或列表值,使其不超过指定的最大长度
162
+
163
+ Args:
164
+ value: 要截断的值,可以是字符串或列表
165
+ max_length: 最大长度限制
166
+ preserve_escapes: 是否保留转义字符
167
+ only_control_chars: 如果为True,仅转义控制字符;如果为False,转义所有非ASCII字符
168
+
169
+ Returns:
170
+ 截断后的值
171
+ """
172
+ if value is None:
173
+ return None
174
+
175
+ # 处理字符串
176
+ if isinstance(value, str):
177
+ # 如果需要保留转义字符
178
+ display_value = value
179
+ if preserve_escapes:
180
+ if only_control_chars:
181
+ # 只转义控制字符,保留其他字符原样
182
+ control_chars = {
183
+ "\n": "\\n",
184
+ "\r": "\\r",
185
+ "\t": "\\t",
186
+ "\b": "\\b",
187
+ "\f": "\\f",
188
+ "\\": "\\\\",
189
+ "\v": "\\v", # 垂直制表符
190
+ "\a": "\\a", # 响铃
191
+ "\0": "\\0", # 空字符
192
+ }
193
+ for char, escape in control_chars.items():
194
+ display_value = display_value.replace(char, escape)
195
+ else:
196
+ # 转义所有特殊字符(包括非ASCII字符如中文)
197
+ display_value = value.encode("unicode_escape").decode("utf-8")
198
+
199
+ if len(display_value) <= max_length:
200
+ return display_value
201
+
202
+ # 如果超过长度,截断中间部分,保留开头和结尾
203
+ half_length = max_length // 2 - 2 # 减2是为了留出"..."的空间
204
+ half_length = max(half_length, 5) # 确保至少有5个字符
205
+ return f"{display_value[:half_length]}...{display_value[-half_length:]}"
206
+
207
+ # 处理列表 - 保留所有元素但截断每个元素的值
208
+ elif isinstance(value, list):
209
+ if not value:
210
+ return []
211
+
212
+ # 计算每个元素最大长度 - 确保每个元素都有展示空间
213
+ item_max_length = max(max_length // len(value), 10) # 确保至少有10个字符
214
+
215
+ return [prettify_value(item, item_max_length, preserve_escapes, only_control_chars) for item in value]
216
+
217
+ # 处理字典 - 保留所有键值对但截断值
218
+ elif isinstance(value, dict):
219
+ result = {}
220
+ item_max_length = max_length // len(value) if value else max_length
221
+ for k, v in value.items():
222
+ # 截断每个值
223
+ result[k] = prettify_value(v, item_max_length, preserve_escapes, only_control_chars)
224
+ return result
225
+
226
+ # 其他类型直接返回
227
+ return value
228
+
229
+
230
+ def format_analysis_result(analysis_result: AnalyseResult, max_value_length: int = 100) -> str:
231
+ """
232
+ 格式化工作流分析结果,生成一个简洁的字符串表示
233
+
234
+ Args:
235
+ analysis_result: 工作流分析结果字典
236
+ max_value_length: 值的最大长度,超过此长度将被截断
237
+
238
+ Returns:
239
+ 格式化后的字符串
240
+ """
241
+ if not analysis_result:
242
+ return "空分析结果"
243
+
244
+ formatted_parts = []
245
+
246
+ # 添加节点信息
247
+ if "nodes" in analysis_result:
248
+ nodes = analysis_result["nodes"]
249
+ formatted_parts.append(f"节点数量: {len(nodes)}")
250
+
251
+ for idx, node in enumerate(nodes):
252
+ truncated_node = {
253
+ "id": node.get("id", "未知ID"),
254
+ "name": node.get("name", "未知名称"),
255
+ "type": node.get("type", "未知类型"),
256
+ "category": node.get("category", "未知类别"),
257
+ }
258
+
259
+ # 添加端口信息摘要
260
+ if "ports" in node:
261
+ ports = node.get("ports", [])
262
+ port_summary = []
263
+
264
+ for port in ports:
265
+ port_info = {
266
+ "name": port.get("name", "未知端口"),
267
+ "type": port.get("type", "未知类型"),
268
+ "connected": port.get("connected", False),
269
+ }
270
+
271
+ # 如果有值且不是隐藏的端口,添加值的摘要
272
+ if "value" in port and port.get("show", True):
273
+ port_info["value"] = prettify_value(port.get("value"), max_value_length)
274
+
275
+ port_summary.append(port_info)
276
+
277
+ truncated_node["ports_summary"] = (
278
+ f"{len(ports)}个端口,其中{sum(1 for p in ports if p.get('connected'))}个已连接"
279
+ )
280
+
281
+ node_str = f"节点{idx + 1}: {json.dumps(truncated_node, ensure_ascii=False, indent=2)}"
282
+ formatted_parts.append(node_str)
283
+
284
+ # 添加其他可能的顶级信息
285
+ for key, value in analysis_result.items():
286
+ if key != "nodes":
287
+ if isinstance(value, (dict, list)):
288
+ summary = f"{key}: 包含{len(value)}个项目"
289
+ else:
290
+ summary = f"{key}: {prettify_value(value, max_value_length)}"
291
+ formatted_parts.append(summary)
292
+
293
+ return "\n".join(formatted_parts)
294
+
295
+
296
+ def format_workflow_analysis_for_llm(analysis_result: AnalyseResult, max_value_length: int = 100) -> str:
297
+ """
298
+ 将工作流分析结果格式化为适合LLM分析的字符串,使用更紧凑的Python风格表示
299
+
300
+ Args:
301
+ analysis_result: 工作流分析结果字典或其JSON字符串表示
302
+ max_value_length: 值的最大长度,超过此长度将被截断
303
+
304
+ Returns:
305
+ 格式化后的字符串
306
+ """
307
+ if not isinstance(analysis_result, dict):
308
+ return f"无效的分析结果类型: {type(analysis_result)}"
309
+
310
+ # 构建LLM友好的格式
311
+ llm_friendly_format = []
312
+
313
+ # 处理节点信息
314
+ if "nodes" in analysis_result:
315
+ nodes = analysis_result["nodes"]
316
+ llm_friendly_format.append("\n## 节点信息")
317
+
318
+ for node in nodes:
319
+ node_id = node.get("id", "未知ID")
320
+ node_name = node.get("name", "未知名称")
321
+ node_type = node.get("type", "未知类型")
322
+ node_category = node.get("category", "未知类别")
323
+
324
+ # 截断节点ID,仅保留前3位和后3位
325
+ short_id = f"{node_id[:3]}...{node_id[-3:]}" if len(node_id) > 6 else node_id
326
+
327
+ node_info = [
328
+ f"- {node_name} <ID: {short_id}>",
329
+ f" - 类型: {node_type}",
330
+ f" - 类别: {node_category}",
331
+ " - 端口",
332
+ ]
333
+
334
+ # 添加端口信息
335
+ if "ports" in node:
336
+ ports = node.get("ports", [])
337
+
338
+ # 遍历所有端口
339
+ for port in ports:
340
+ port_name = port.get("name", "未知端口")
341
+ port_type = port.get("type", "未知类型")
342
+
343
+ port_line = f" - `{port_name}` ({port_type})"
344
+ node_info.append(port_line)
345
+
346
+ # 添加值的紧凑描述
347
+ if "value" in port:
348
+ value = port.get("value")
349
+ if isinstance(value, list):
350
+ # 列表值,使用Python风格显示
351
+ truncated_list = []
352
+ item_max_length = max(max_value_length // len(value) if value else max_value_length, 10)
353
+ node_info.append(
354
+ f" - value(list): {json.dumps(prettify_value(value, item_max_length), ensure_ascii=False)}"
355
+ )
356
+ elif isinstance(value, dict):
357
+ # 字典值,使用Python风格显示
358
+ dict_items = []
359
+ key_max_length = max(max_value_length // len(value) if value else max_value_length, 10)
360
+ node_info.append(
361
+ f" - value(dict): {json.dumps(prettify_value(value, key_max_length), ensure_ascii=False)}"
362
+ )
363
+ elif isinstance(value, str):
364
+ # 字符串值,带引号
365
+ truncated = prettify_value(value, max_value_length)
366
+ node_info.append(f' - value(str): "{truncated}"')
367
+ else:
368
+ # 其他类型的值
369
+ type_name = type(value).__name__
370
+ node_info.append(f" - value({type_name}): {value}")
371
+ else:
372
+ # 如果没有端口,移除端口标题
373
+ node_info.pop()
374
+
375
+ llm_friendly_format.append("\n".join(node_info))
376
+
377
+ # 添加其他可能的顶级信息
378
+ other_info = []
379
+ for key, value in analysis_result.items():
380
+ if key != "nodes":
381
+ if isinstance(value, dict):
382
+ # 字典,Python风格显示
383
+ dict_items = []
384
+ key_max_length = max(max_value_length // len(value) if value else max_value_length, 10)
385
+
386
+ for k, v in value.items():
387
+ if isinstance(v, str):
388
+ truncated_v = f'"{prettify_value(v, key_max_length)}"'
389
+ else:
390
+ truncated_v = str(prettify_value(v, key_max_length))
391
+ dict_items.append(f'"{k}": {truncated_v}')
392
+
393
+ dict_repr = "{" + ", ".join(dict_items) + "}"
394
+ other_info.append(f"- **{key}**(dict): {dict_repr}")
395
+ elif isinstance(value, list):
396
+ # 列表,Python风格显示
397
+ truncated_list = []
398
+ item_max_length = max(max_value_length // len(value) if value else max_value_length, 10)
399
+
400
+ for item in value:
401
+ if isinstance(item, str):
402
+ truncated_item = f'"{prettify_value(item, item_max_length)}"'
403
+ else:
404
+ truncated_item = str(prettify_value(item, item_max_length))
405
+ truncated_list.append(truncated_item)
406
+
407
+ list_repr = "[" + ", ".join(truncated_list) + "]"
408
+ other_info.append(f"- **{key}**(list): {list_repr}")
409
+ else:
410
+ # 基本类型
411
+ type_name = type(value).__name__
412
+ if isinstance(value, str):
413
+ truncated = prettify_value(value, max_value_length)
414
+ other_info.append(f'- **{key}**({type_name}): "{truncated}"')
415
+ else:
416
+ other_info.append(f"- **{key}**({type_name}): {prettify_value(value, max_value_length)}")
417
+
418
+ if other_info:
419
+ llm_friendly_format.append("\n### 其他信息")
420
+ llm_friendly_format.append("\n".join(other_info))
421
+
422
+ return "\n".join(llm_friendly_format)
@@ -25,6 +25,7 @@ class WorkflowCheckResult(TypedDict):
25
25
  ui_warnings: UIWarning # UI相关警告
26
26
  required_ports: list[tuple["Node", "Port"]] # 未连接的必填端口
27
27
  override_ports: list[tuple["Node", "Port"]] # 被覆盖的端口
28
+ output_nodes_with_no_inputs: list["Node"] # 输出端口没有输入端口连接的节点
28
29
 
29
30
 
30
31
  def check_dag(workflow: "Workflow"):
@@ -259,3 +260,30 @@ def check_override_ports(workflow: "Workflow") -> list[tuple["Node", "Port"]]:
259
260
  override_ports.append((node, port))
260
261
 
261
262
  return override_ports
263
+
264
+
265
+ def check_output_nodes_with_no_inputs(workflow: "Workflow") -> list["Node"]:
266
+ """检查工作流中是否存在输出端口没有输入端口连接的节点。"""
267
+ output_nodes_with_no_inputs = []
268
+
269
+ # 找出所有连接的目标节点和端口
270
+ connected_targets = {(edge.target, edge.target_handle) for edge in workflow.edges}
271
+
272
+ # 遍历所有节点
273
+ for node in workflow.nodes:
274
+ # 只检查输出类节点
275
+ if hasattr(node, "category") and node.category == "outputs":
276
+ # 检查该节点是否有任何连接到其输入端口的边
277
+ has_input_connection = False
278
+
279
+ for port_name, port in node.ports.items():
280
+ if isinstance(port, InputPort):
281
+ if (node.id, port_name) in connected_targets:
282
+ has_input_connection = True
283
+ break
284
+
285
+ # 如果该输出节点没有任何输入连接,则添加到结果列表
286
+ if not has_input_connection:
287
+ output_nodes_with_no_inputs.append(node)
288
+
289
+ return output_nodes_with_no_inputs
@@ -1,115 +0,0 @@
1
- import json
2
- from typing import TypedDict, Any
3
-
4
-
5
- class PortRecord(TypedDict):
6
- """
7
- 端口记录
8
- """
9
-
10
- name: str
11
- type: str
12
- show: bool
13
- value: Any
14
- connected: bool
15
-
16
-
17
- class NodeRecord(TypedDict):
18
- """
19
- 节点记录
20
- """
21
-
22
- id: str
23
- name: str
24
- type: str
25
- category: str
26
- ports: list[PortRecord]
27
-
28
-
29
- class AnalyseResult(TypedDict):
30
- """
31
- 分析结果
32
- """
33
-
34
- nodes: list[NodeRecord]
35
-
36
-
37
- def analyse_workflow_record(
38
- json_str: str, connected_only: bool = False, reserver_programming_function_ports: bool = False
39
- ) -> AnalyseResult:
40
- """
41
- 分析工作流JSON字符串,提取节点和端口信息
42
-
43
- Args:
44
- json_str: 工作流JSON字符串
45
-
46
- Returns:
47
- 分析结果
48
- """
49
- # 解析JSON
50
- workflow_data = json.loads(json_str)
51
-
52
- # 收集所有连接的端口
53
- connected_ports = set()
54
- for edge in workflow_data["edges"]:
55
- source_id = edge["source"]
56
- target_id = edge["target"]
57
- source_handle = edge["sourceHandle"]
58
- target_handle = edge["targetHandle"]
59
- connected_ports.add((source_id, source_handle))
60
- connected_ports.add((target_id, target_handle))
61
-
62
- # 分析节点
63
- nodes_records = []
64
-
65
- for node in workflow_data["nodes"]:
66
- node_id = node["id"]
67
- node_type = node["type"]
68
- category = node["category"]
69
-
70
- # 跳过辅助节点
71
- if category == "assistedNodes":
72
- continue
73
-
74
- # 获取任务名称
75
- task_name = node["data"]["task_name"].split(".")[-1] if "task_name" in node["data"] else ""
76
-
77
- # 收集端口信息
78
- ports_records = []
79
-
80
- if "template" in node["data"]:
81
- for port_name, port in node["data"]["template"].items():
82
- if "name" not in port:
83
- continue
84
-
85
- port_is_connected = (node_id, port["name"]) in connected_ports
86
-
87
- if node_type != "ProgrammingFunction" or not reserver_programming_function_ports:
88
- if connected_only and not port_is_connected:
89
- continue
90
-
91
- port_record: PortRecord = {
92
- "name": port["name"],
93
- "type": port.get("field_type", port.get("type", "")),
94
- "show": port.get("show", False),
95
- "value": port.get("value", None),
96
- "connected": port_is_connected,
97
- }
98
-
99
- ports_records.append(port_record)
100
-
101
- # 创建节点记录
102
- node_record: NodeRecord = {
103
- "id": node_id,
104
- "name": task_name,
105
- "type": node_type,
106
- "category": category,
107
- "ports": ports_records,
108
- }
109
-
110
- nodes_records.append(node_record)
111
-
112
- # 返回分析结果
113
- result: AnalyseResult = {"nodes": nodes_records}
114
-
115
- return result
File without changes