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.
- {vectorvein-0.2.71 → vectorvein-0.2.73}/PKG-INFO +1 -1
- {vectorvein-0.2.71 → vectorvein-0.2.73}/pyproject.toml +1 -1
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/graph/workflow.py +3 -0
- vectorvein-0.2.73/src/vectorvein/workflow/utils/analyse.py +422 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/utils/check.py +28 -0
- vectorvein-0.2.71/src/vectorvein/workflow/utils/analyse.py +0 -115
- {vectorvein-0.2.71 → vectorvein-0.2.73}/README.md +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/__init__.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/api/__init__.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/api/client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/api/exceptions.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/api/models.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/__init__.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/anthropic_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/baichuan_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/base_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/deepseek_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/ernie_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/gemini_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/groq_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/local_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/minimax_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/mistral_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/moonshot_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/openai_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/openai_compatible_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/py.typed +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/qwen_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/stepfun_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/utils.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/xai_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/yi_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/chat_clients/zhipuai_client.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/py.typed +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/server/token_server.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/settings/__init__.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/settings/py.typed +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/__init__.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/defaults.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/enums.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/exception.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/llm_parameters.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/py.typed +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/types/settings.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/utilities/media_processing.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/utilities/rate_limiter.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/utilities/retry.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/graph/edge.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/graph/node.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/graph/port.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/__init__.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/audio_generation.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/control_flows.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/file_processing.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/image_generation.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/llms.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/media_editing.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/media_processing.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/output.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/relational_db.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/text_processing.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/tools.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/triggers.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/vector_db.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/video_generation.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/nodes/web_crawlers.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/utils/json_to_code.py +0 -0
- {vectorvein-0.2.71 → vectorvein-0.2.73}/src/vectorvein/workflow/utils/layout.py +0 -0
@@ -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
|
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.71 → vectorvein-0.2.73}/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
|