vectorvein 0.2.54__py3-none-any.whl → 0.2.56__py3-none-any.whl

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.
@@ -764,9 +764,14 @@ class AnthropicChatClient(BaseChatClient):
764
764
  "reasoning_content": "",
765
765
  "raw_content": [content_block.model_dump() for content_block in response.content],
766
766
  "usage": {
767
- "prompt_tokens": response.usage.input_tokens,
767
+ "prompt_tokens": response.usage.input_tokens + response.usage.cache_read_input_tokens
768
+ if response.usage.cache_read_input_tokens
769
+ else 0,
768
770
  "completion_tokens": response.usage.output_tokens,
769
771
  "total_tokens": response.usage.input_tokens + response.usage.output_tokens,
772
+ "prompt_tokens_details": {
773
+ "cached_tokens": response.usage.cache_read_input_tokens,
774
+ },
770
775
  },
771
776
  }
772
777
  tool_calls = []
@@ -1370,9 +1375,14 @@ class AsyncAnthropicChatClient(BaseAsyncChatClient):
1370
1375
  "reasoning_content": "",
1371
1376
  "raw_content": [content_block.model_dump() for content_block in response.content],
1372
1377
  "usage": {
1373
- "prompt_tokens": response.usage.input_tokens,
1378
+ "prompt_tokens": response.usage.input_tokens + response.usage.cache_read_input_tokens
1379
+ if response.usage.cache_read_input_tokens
1380
+ else 0,
1374
1381
  "completion_tokens": response.usage.output_tokens,
1375
1382
  "total_tokens": response.usage.input_tokens + response.usage.output_tokens,
1383
+ "prompt_tokens_details": {
1384
+ "cached_tokens": response.usage.cache_read_input_tokens,
1385
+ },
1376
1386
  },
1377
1387
  }
1378
1388
  tool_calls = []
@@ -118,6 +118,12 @@ class Node:
118
118
  "shadow": self.shadow,
119
119
  }
120
120
 
121
+ def __str__(self) -> str:
122
+ return f"Node<{self.id}:{self.type}>"
123
+
124
+ def __repr__(self) -> str:
125
+ return self.__str__()
126
+
121
127
  def has_inputs(self) -> bool:
122
128
  for port in self.ports.values():
123
129
  if isinstance(port, InputPort):
@@ -4,7 +4,7 @@ from typing import List, Union, Dict, Any, Optional
4
4
  from .node import Node
5
5
  from .edge import Edge
6
6
  from ..utils.layout import layout
7
- from ..utils.check import WorkflowCheckResult, check_dag, check_ui
7
+ from ..utils.check import WorkflowCheckResult, check_dag, check_ui, check_useless_nodes
8
8
 
9
9
 
10
10
  class Workflow:
@@ -127,10 +127,15 @@ class Workflow:
127
127
  """
128
128
  dag_check = check_dag(self) # 检查流程图是否为有向无环图,并检测是否存在孤立节点。
129
129
  ui_check = check_ui(self)
130
+ useless_nodes = check_useless_nodes(self)
130
131
 
131
132
  # 合并结果
132
- result = dag_check
133
- result["ui_warnings"] = ui_check
133
+ result: WorkflowCheckResult = {
134
+ "no_cycle": dag_check["no_cycle"],
135
+ "no_isolated_nodes": dag_check["no_isolated_nodes"],
136
+ "ui_warnings": ui_check,
137
+ "useless_nodes": useless_nodes,
138
+ }
134
139
 
135
140
  return result
136
141
 
@@ -1,356 +1,356 @@
1
- from typing import Optional
2
-
3
- from ..graph.node import Node
4
- from ..graph.port import PortType, InputPort, OutputPort
5
-
6
-
7
- class CodebaseAnalysis(Node):
8
- def __init__(self, id: Optional[str] = None):
9
- super().__init__(
10
- node_type="CodebaseAnalysis",
11
- category="tools",
12
- task_name="tools.codebase_analysis",
13
- node_id=id,
14
- ports={
15
- "input_type": InputPort(
16
- name="input_type",
17
- port_type=PortType.SELECT,
18
- value="file",
19
- options=[
20
- {"value": "file", "label": "file"},
21
- {"value": "git_url", "label": "git_url"},
22
- ],
23
- ),
24
- "codebase_file": InputPort(
25
- name="codebase_file",
26
- port_type=PortType.FILE,
27
- value=list(),
28
- support_file_types=[".zip"],
29
- multiple=False,
30
- condition="return fieldsData.input_type.value === 'file'",
31
- ),
32
- "git_url": InputPort(
33
- name="git_url",
34
- port_type=PortType.INPUT,
35
- value="",
36
- condition="return fieldsData.input_type.value === 'git_url'",
37
- ),
38
- "output_style": InputPort(
39
- name="output_style",
40
- port_type=PortType.SELECT,
41
- value="markdown",
42
- options=[
43
- {"value": "plain", "label": "Plain Text"},
44
- {"value": "xml", "label": "XML"},
45
- {"value": "markdown", "label": "Markdown"},
46
- ],
47
- ),
48
- "show_line_numbers": InputPort(
49
- name="show_line_numbers",
50
- port_type=PortType.CHECKBOX,
51
- value=False,
52
- ),
53
- "remove_comments": InputPort(
54
- name="remove_comments",
55
- port_type=PortType.CHECKBOX,
56
- value=False,
57
- ),
58
- "remove_empty_lines": InputPort(
59
- name="remove_empty_lines",
60
- port_type=PortType.CHECKBOX,
61
- value=False,
62
- ),
63
- "ignore_patterns": InputPort(
64
- name="ignore_patterns",
65
- port_type=PortType.INPUT,
66
- value=list(),
67
- multiple=True,
68
- ),
69
- "output": OutputPort(),
70
- },
71
- )
72
-
73
-
74
- class TextTranslation(Node):
75
- def __init__(self, id: Optional[str] = None):
76
- super().__init__(
77
- node_type="TextTranslation",
78
- category="tools",
79
- task_name="tools.text_translation",
80
- node_id=id,
81
- ports={
82
- "text": InputPort(
83
- name="text",
84
- port_type=PortType.INPUT,
85
- value="",
86
- field_type="textarea",
87
- ),
88
- "from_language": InputPort(
89
- name="from_language",
90
- port_type=PortType.SELECT,
91
- value="auto",
92
- options=[
93
- {"value": "ar", "label": "ar"},
94
- {"value": "de", "label": "de"},
95
- {"value": "en", "label": "en"},
96
- {"value": "es", "label": "es"},
97
- {"value": "fr", "label": "fr"},
98
- {"value": "hi", "label": "hi"},
99
- {"value": "id", "label": "id"},
100
- {"value": "it", "label": "it"},
101
- {"value": "ja", "label": "ja"},
102
- {"value": "ko", "label": "ko"},
103
- {"value": "nl", "label": "nl"},
104
- {"value": "pt", "label": "pt"},
105
- {"value": "ru", "label": "ru"},
106
- {"value": "th", "label": "th"},
107
- {"value": "vi", "label": "vi"},
108
- {"value": "zh-CHS", "label": "zh-CHS"},
109
- {"value": "zh-CHT", "label": "zh-CHT"},
110
- {"value": "auto", "label": "auto"},
111
- ],
112
- ),
113
- "to_language": InputPort(
114
- name="to_language",
115
- port_type=PortType.SELECT,
116
- value="en",
117
- options=[
118
- {"value": "ar", "label": "ar"},
119
- {"value": "de", "label": "de"},
120
- {"value": "en", "label": "en"},
121
- {"value": "es", "label": "es"},
122
- {"value": "fr", "label": "fr"},
123
- {"value": "hi", "label": "hi"},
124
- {"value": "id", "label": "id"},
125
- {"value": "it", "label": "it"},
126
- {"value": "ja", "label": "ja"},
127
- {"value": "ko", "label": "ko"},
128
- {"value": "nl", "label": "nl"},
129
- {"value": "pt", "label": "pt"},
130
- {"value": "ru", "label": "ru"},
131
- {"value": "th", "label": "th"},
132
- {"value": "vi", "label": "vi"},
133
- {"value": "zh-CHS", "label": "zh-CHS"},
134
- {"value": "zh-CHT", "label": "zh-CHT"},
135
- ],
136
- ),
137
- "output": OutputPort(),
138
- },
139
- )
140
-
141
-
142
- class TextSearch(Node):
143
- def __init__(self, id: Optional[str] = None):
144
- super().__init__(
145
- node_type="TextSearch",
146
- category="tools",
147
- task_name="tools.text_search",
148
- node_id=id,
149
- ports={
150
- "search_text": InputPort(
151
- name="search_text",
152
- port_type=PortType.INPUT,
153
- value="",
154
- ),
155
- "search_engine": InputPort(
156
- name="search_engine",
157
- port_type=PortType.SELECT,
158
- value="bing",
159
- options=[
160
- {"value": "bing", "label": "bing"},
161
- {"value": "bochaai", "label": "bochaai"},
162
- {"value": "jina.ai", "label": "jina.ai"},
163
- {"value": "zhipuai", "label": "zhipuai"},
164
- {"value": "duckduckgo", "label": "duckduckgo"},
165
- ],
166
- ),
167
- "count": InputPort(
168
- name="count",
169
- port_type=PortType.NUMBER,
170
- value=10,
171
- ),
172
- "offset": InputPort(
173
- name="offset",
174
- port_type=PortType.NUMBER,
175
- value=0,
176
- ),
177
- "freshness": InputPort(
178
- name="freshness",
179
- port_type=PortType.SELECT,
180
- value="all",
181
- options=[
182
- {"value": "all", "label": "all"},
183
- {"value": "day", "label": "day"},
184
- {"value": "week", "label": "week"},
185
- {"value": "month", "label": "month"},
186
- {"value": "custom", "label": "custom"},
187
- ],
188
- condition="return fieldsData.search_engine.value === 'bing'",
189
- ),
190
- "custom_freshness": InputPort(
191
- name="custom_freshness",
192
- port_type=PortType.INPUT,
193
- value="",
194
- condition="return fieldsData.freshness.value === 'custom'",
195
- ),
196
- "combine_result_in_text": InputPort(
197
- name="combine_result_in_text",
198
- port_type=PortType.CHECKBOX,
199
- value=True,
200
- ),
201
- "max_snippet_length": InputPort(
202
- name="max_snippet_length",
203
- port_type=PortType.NUMBER,
204
- value=300,
205
- ),
206
- "output_type": InputPort(
207
- name="output_type",
208
- port_type=PortType.SELECT,
209
- value="markdown",
210
- options=[
211
- {"value": "text", "label": "text"},
212
- {"value": "markdown", "label": "markdown"},
213
- ],
214
- ),
215
- "output_page_title": OutputPort(
216
- name="output_page_title",
217
- port_type=PortType.LIST,
218
- ),
219
- "output_page_url": OutputPort(
220
- name="output_page_url",
221
- port_type=PortType.LIST,
222
- ),
223
- "output_page_snippet": OutputPort(
224
- name="output_page_snippet",
225
- port_type=PortType.LIST,
226
- ),
227
- },
228
- )
229
-
230
-
231
- class ProgrammingFunction(Node):
232
- def __init__(self, id: Optional[str] = None):
233
- super().__init__(
234
- node_type="ProgrammingFunction",
235
- category="tools",
236
- task_name="tools.programming_function",
237
- node_id=id,
238
- ports={
239
- "language": InputPort(
240
- name="language",
241
- port_type=PortType.SELECT,
242
- value="python",
243
- options=[
244
- {"value": "python", "label": "Python"},
245
- ],
246
- ),
247
- "code": InputPort(
248
- name="code",
249
- port_type=PortType.INPUT,
250
- value="",
251
- field_type="textarea",
252
- ),
253
- "use_oversea_node": InputPort(
254
- name="use_oversea_node",
255
- port_type=PortType.CHECKBOX,
256
- value=False,
257
- ),
258
- "list_input": InputPort(
259
- name="list_input",
260
- port_type=PortType.CHECKBOX,
261
- value=False,
262
- ),
263
- "instance_type": InputPort(
264
- name="instance_type",
265
- port_type=PortType.SELECT,
266
- value="light",
267
- options=[
268
- {"value": "light", "label": "light"},
269
- {"value": "large", "label": "large"},
270
- ],
271
- ),
272
- "output": OutputPort(
273
- name="output",
274
- port_type=PortType.INPUT,
275
- field_type="textarea",
276
- ),
277
- "console_msg": OutputPort(
278
- name="console_msg",
279
- port_type=PortType.INPUT,
280
- field_type="textarea",
281
- ),
282
- "error_msg": OutputPort(
283
- name="error_msg",
284
- port_type=PortType.INPUT,
285
- field_type="textarea",
286
- ),
287
- "files": OutputPort(
288
- name="files",
289
- port_type=PortType.INPUT,
290
- field_type="textarea",
291
- ),
292
- },
293
- can_add_input_ports=True,
294
- )
295
-
296
-
297
- class ImageSearch(Node):
298
- def __init__(self, id: Optional[str] = None):
299
- super().__init__(
300
- node_type="ImageSearch",
301
- category="tools",
302
- task_name="tools.image_search",
303
- node_id=id,
304
- ports={
305
- "search_text": InputPort(
306
- name="search_text",
307
- port_type=PortType.INPUT,
308
- value="",
309
- ),
310
- "search_engine": InputPort(
311
- name="search_engine",
312
- port_type=PortType.SELECT,
313
- value="bing",
314
- options=[
315
- {"value": "bing", "label": "bing"},
316
- {"value": "pexels", "label": "pexels"},
317
- {"value": "unsplash", "label": "unsplash"},
318
- ],
319
- ),
320
- "count": InputPort(
321
- name="count",
322
- port_type=PortType.NUMBER,
323
- value=5,
324
- ),
325
- "output_type": InputPort(
326
- name="output_type",
327
- port_type=PortType.SELECT,
328
- value="markdown",
329
- options=[
330
- {"value": "text", "label": "text"},
331
- {"value": "markdown", "label": "markdown"},
332
- ],
333
- ),
334
- "output": OutputPort(
335
- name="output",
336
- port_type=PortType.LIST,
337
- ),
338
- },
339
- )
340
-
341
-
342
- class WorkflowInvoke(Node):
343
- def __init__(self, id: Optional[str] = None):
344
- super().__init__(
345
- node_type="WorkflowInvoke",
346
- category="tools",
347
- task_name="tools.workflow_invoke",
348
- node_id=id,
349
- ports={
350
- "workflow_id": InputPort(
351
- name="workflow_id",
352
- port_type=PortType.INPUT,
353
- value="",
354
- ),
355
- },
356
- )
1
+ from typing import Optional
2
+
3
+ from ..graph.node import Node
4
+ from ..graph.port import PortType, InputPort, OutputPort
5
+
6
+
7
+ class CodebaseAnalysis(Node):
8
+ def __init__(self, id: Optional[str] = None):
9
+ super().__init__(
10
+ node_type="CodebaseAnalysis",
11
+ category="tools",
12
+ task_name="tools.codebase_analysis",
13
+ node_id=id,
14
+ ports={
15
+ "input_type": InputPort(
16
+ name="input_type",
17
+ port_type=PortType.SELECT,
18
+ value="file",
19
+ options=[
20
+ {"value": "file", "label": "file"},
21
+ {"value": "git_url", "label": "git_url"},
22
+ ],
23
+ ),
24
+ "codebase_file": InputPort(
25
+ name="codebase_file",
26
+ port_type=PortType.FILE,
27
+ value=list(),
28
+ support_file_types=[".zip"],
29
+ multiple=False,
30
+ condition="return fieldsData.input_type.value === 'file'",
31
+ ),
32
+ "git_url": InputPort(
33
+ name="git_url",
34
+ port_type=PortType.INPUT,
35
+ value="",
36
+ condition="return fieldsData.input_type.value === 'git_url'",
37
+ ),
38
+ "output_style": InputPort(
39
+ name="output_style",
40
+ port_type=PortType.SELECT,
41
+ value="markdown",
42
+ options=[
43
+ {"value": "plain", "label": "Plain Text"},
44
+ {"value": "xml", "label": "XML"},
45
+ {"value": "markdown", "label": "Markdown"},
46
+ ],
47
+ ),
48
+ "show_line_numbers": InputPort(
49
+ name="show_line_numbers",
50
+ port_type=PortType.CHECKBOX,
51
+ value=False,
52
+ ),
53
+ "remove_comments": InputPort(
54
+ name="remove_comments",
55
+ port_type=PortType.CHECKBOX,
56
+ value=False,
57
+ ),
58
+ "remove_empty_lines": InputPort(
59
+ name="remove_empty_lines",
60
+ port_type=PortType.CHECKBOX,
61
+ value=False,
62
+ ),
63
+ "ignore_patterns": InputPort(
64
+ name="ignore_patterns",
65
+ port_type=PortType.INPUT,
66
+ value=list(),
67
+ multiple=True,
68
+ ),
69
+ "output": OutputPort(),
70
+ },
71
+ )
72
+
73
+
74
+ class TextTranslation(Node):
75
+ def __init__(self, id: Optional[str] = None):
76
+ super().__init__(
77
+ node_type="TextTranslation",
78
+ category="tools",
79
+ task_name="tools.text_translation",
80
+ node_id=id,
81
+ ports={
82
+ "text": InputPort(
83
+ name="text",
84
+ port_type=PortType.INPUT,
85
+ value="",
86
+ field_type="textarea",
87
+ ),
88
+ "from_language": InputPort(
89
+ name="from_language",
90
+ port_type=PortType.SELECT,
91
+ value="auto",
92
+ options=[
93
+ {"value": "ar", "label": "ar"},
94
+ {"value": "de", "label": "de"},
95
+ {"value": "en", "label": "en"},
96
+ {"value": "es", "label": "es"},
97
+ {"value": "fr", "label": "fr"},
98
+ {"value": "hi", "label": "hi"},
99
+ {"value": "id", "label": "id"},
100
+ {"value": "it", "label": "it"},
101
+ {"value": "ja", "label": "ja"},
102
+ {"value": "ko", "label": "ko"},
103
+ {"value": "nl", "label": "nl"},
104
+ {"value": "pt", "label": "pt"},
105
+ {"value": "ru", "label": "ru"},
106
+ {"value": "th", "label": "th"},
107
+ {"value": "vi", "label": "vi"},
108
+ {"value": "zh-CHS", "label": "zh-CHS"},
109
+ {"value": "zh-CHT", "label": "zh-CHT"},
110
+ {"value": "auto", "label": "auto"},
111
+ ],
112
+ ),
113
+ "to_language": InputPort(
114
+ name="to_language",
115
+ port_type=PortType.SELECT,
116
+ value="en",
117
+ options=[
118
+ {"value": "ar", "label": "ar"},
119
+ {"value": "de", "label": "de"},
120
+ {"value": "en", "label": "en"},
121
+ {"value": "es", "label": "es"},
122
+ {"value": "fr", "label": "fr"},
123
+ {"value": "hi", "label": "hi"},
124
+ {"value": "id", "label": "id"},
125
+ {"value": "it", "label": "it"},
126
+ {"value": "ja", "label": "ja"},
127
+ {"value": "ko", "label": "ko"},
128
+ {"value": "nl", "label": "nl"},
129
+ {"value": "pt", "label": "pt"},
130
+ {"value": "ru", "label": "ru"},
131
+ {"value": "th", "label": "th"},
132
+ {"value": "vi", "label": "vi"},
133
+ {"value": "zh-CHS", "label": "zh-CHS"},
134
+ {"value": "zh-CHT", "label": "zh-CHT"},
135
+ ],
136
+ ),
137
+ "output": OutputPort(),
138
+ },
139
+ )
140
+
141
+
142
+ class TextSearch(Node):
143
+ def __init__(self, id: Optional[str] = None):
144
+ super().__init__(
145
+ node_type="TextSearch",
146
+ category="tools",
147
+ task_name="tools.text_search",
148
+ node_id=id,
149
+ ports={
150
+ "search_text": InputPort(
151
+ name="search_text",
152
+ port_type=PortType.INPUT,
153
+ value="",
154
+ ),
155
+ "search_engine": InputPort(
156
+ name="search_engine",
157
+ port_type=PortType.SELECT,
158
+ value="bing",
159
+ options=[
160
+ {"value": "bing", "label": "bing"},
161
+ {"value": "bochaai", "label": "bochaai"},
162
+ {"value": "jina.ai", "label": "jina.ai"},
163
+ {"value": "zhipuai", "label": "zhipuai"},
164
+ {"value": "duckduckgo", "label": "duckduckgo"},
165
+ ],
166
+ ),
167
+ "count": InputPort(
168
+ name="count",
169
+ port_type=PortType.NUMBER,
170
+ value=10,
171
+ ),
172
+ "offset": InputPort(
173
+ name="offset",
174
+ port_type=PortType.NUMBER,
175
+ value=0,
176
+ ),
177
+ "freshness": InputPort(
178
+ name="freshness",
179
+ port_type=PortType.SELECT,
180
+ value="all",
181
+ options=[
182
+ {"value": "all", "label": "all"},
183
+ {"value": "day", "label": "day"},
184
+ {"value": "week", "label": "week"},
185
+ {"value": "month", "label": "month"},
186
+ {"value": "custom", "label": "custom"},
187
+ ],
188
+ condition="return fieldsData.search_engine.value === 'bing'",
189
+ ),
190
+ "custom_freshness": InputPort(
191
+ name="custom_freshness",
192
+ port_type=PortType.INPUT,
193
+ value="",
194
+ condition="return fieldsData.freshness.value === 'custom'",
195
+ ),
196
+ "combine_result_in_text": InputPort(
197
+ name="combine_result_in_text",
198
+ port_type=PortType.CHECKBOX,
199
+ value=True,
200
+ ),
201
+ "max_snippet_length": InputPort(
202
+ name="max_snippet_length",
203
+ port_type=PortType.NUMBER,
204
+ value=300,
205
+ ),
206
+ "output_type": InputPort(
207
+ name="output_type",
208
+ port_type=PortType.SELECT,
209
+ value="markdown",
210
+ options=[
211
+ {"value": "text", "label": "text"},
212
+ {"value": "markdown", "label": "markdown"},
213
+ ],
214
+ ),
215
+ "output_page_title": OutputPort(
216
+ name="output_page_title",
217
+ port_type=PortType.LIST,
218
+ ),
219
+ "output_page_url": OutputPort(
220
+ name="output_page_url",
221
+ port_type=PortType.LIST,
222
+ ),
223
+ "output_page_snippet": OutputPort(
224
+ name="output_page_snippet",
225
+ port_type=PortType.LIST,
226
+ ),
227
+ },
228
+ )
229
+
230
+
231
+ class ProgrammingFunction(Node):
232
+ def __init__(self, id: Optional[str] = None):
233
+ super().__init__(
234
+ node_type="ProgrammingFunction",
235
+ category="tools",
236
+ task_name="tools.programming_function",
237
+ node_id=id,
238
+ ports={
239
+ "language": InputPort(
240
+ name="language",
241
+ port_type=PortType.SELECT,
242
+ value="python",
243
+ options=[
244
+ {"value": "python", "label": "Python"},
245
+ ],
246
+ ),
247
+ "code": InputPort(
248
+ name="code",
249
+ port_type=PortType.INPUT,
250
+ value="",
251
+ field_type="textarea",
252
+ ),
253
+ "use_oversea_node": InputPort(
254
+ name="use_oversea_node",
255
+ port_type=PortType.CHECKBOX,
256
+ value=False,
257
+ ),
258
+ "list_input": InputPort(
259
+ name="list_input",
260
+ port_type=PortType.CHECKBOX,
261
+ value=False,
262
+ ),
263
+ "instance_type": InputPort(
264
+ name="instance_type",
265
+ port_type=PortType.SELECT,
266
+ value="light",
267
+ options=[
268
+ {"value": "light", "label": "light"},
269
+ {"value": "large", "label": "large"},
270
+ ],
271
+ ),
272
+ "output": OutputPort(
273
+ name="output",
274
+ port_type=PortType.INPUT,
275
+ field_type="textarea",
276
+ ),
277
+ "console_msg": OutputPort(
278
+ name="console_msg",
279
+ port_type=PortType.INPUT,
280
+ field_type="textarea",
281
+ ),
282
+ "error_msg": OutputPort(
283
+ name="error_msg",
284
+ port_type=PortType.INPUT,
285
+ field_type="textarea",
286
+ ),
287
+ "files": OutputPort(
288
+ name="files",
289
+ port_type=PortType.INPUT,
290
+ field_type="textarea",
291
+ ),
292
+ },
293
+ can_add_input_ports=True,
294
+ )
295
+
296
+
297
+ class ImageSearch(Node):
298
+ def __init__(self, id: Optional[str] = None):
299
+ super().__init__(
300
+ node_type="ImageSearch",
301
+ category="tools",
302
+ task_name="tools.image_search",
303
+ node_id=id,
304
+ ports={
305
+ "search_text": InputPort(
306
+ name="search_text",
307
+ port_type=PortType.INPUT,
308
+ value="",
309
+ ),
310
+ "search_engine": InputPort(
311
+ name="search_engine",
312
+ port_type=PortType.SELECT,
313
+ value="bing",
314
+ options=[
315
+ {"value": "bing", "label": "bing"},
316
+ {"value": "pexels", "label": "pexels"},
317
+ {"value": "unsplash", "label": "unsplash"},
318
+ ],
319
+ ),
320
+ "count": InputPort(
321
+ name="count",
322
+ port_type=PortType.NUMBER,
323
+ value=5,
324
+ ),
325
+ "output_type": InputPort(
326
+ name="output_type",
327
+ port_type=PortType.SELECT,
328
+ value="markdown",
329
+ options=[
330
+ {"value": "text", "label": "text"},
331
+ {"value": "markdown", "label": "markdown"},
332
+ ],
333
+ ),
334
+ "output": OutputPort(
335
+ name="output",
336
+ port_type=PortType.LIST,
337
+ ),
338
+ },
339
+ )
340
+
341
+
342
+ class WorkflowInvoke(Node):
343
+ def __init__(self, id: Optional[str] = None):
344
+ super().__init__(
345
+ node_type="WorkflowInvoke",
346
+ category="tools",
347
+ task_name="tools.workflow_invoke",
348
+ node_id=id,
349
+ ports={
350
+ "workflow_id": InputPort(
351
+ name="workflow_id",
352
+ port_type=PortType.INPUT,
353
+ value="",
354
+ ),
355
+ },
356
+ )
@@ -4,6 +4,7 @@ from ..graph.port import InputPort
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from ..graph.workflow import Workflow
7
+ from ..graph.node import Node
7
8
 
8
9
 
9
10
  class UIWarning(TypedDict, total=False):
@@ -14,23 +15,24 @@ class UIWarning(TypedDict, total=False):
14
15
  has_output_nodes: bool # 是否存在输出节点
15
16
 
16
17
 
17
- class WorkflowCheckResult(TypedDict, total=False):
18
+ class WorkflowCheckResult(TypedDict):
18
19
  """工作流检查结果类型。"""
19
20
 
20
21
  no_cycle: bool # 工作流是否不包含环
21
22
  no_isolated_nodes: bool # 工作流是否不包含孤立节点
23
+ useless_nodes: list["Node"] # 工作流中无用的节点
22
24
  ui_warnings: UIWarning # UI相关警告
23
25
 
24
26
 
25
- def check_dag(workflow: "Workflow") -> WorkflowCheckResult:
27
+ def check_dag(workflow: "Workflow"):
26
28
  """检查流程图是否为有向无环图,并检测是否存在孤立节点。
27
29
 
28
30
  Returns:
29
- WorkflowCheckResult: 包含检查结果的字典
31
+ 包含检查结果的字典
30
32
  - no_cycle (bool): 如果流程图是有向无环图返回 True,否则返回 False
31
33
  - no_isolated_nodes (bool): 如果不存在孤立节点返回 True,否则返回 False
32
34
  """
33
- result: WorkflowCheckResult = {"no_cycle": True, "no_isolated_nodes": True}
35
+ result = {"no_cycle": True, "no_isolated_nodes": True}
34
36
 
35
37
  # 过滤掉触发器节点和辅助节点
36
38
  trigger_nodes = [
@@ -159,3 +161,22 @@ def check_ui(workflow: "Workflow") -> UIWarning:
159
161
  warnings["has_shown_input_ports"] = has_shown_input_ports
160
162
 
161
163
  return warnings
164
+
165
+
166
+ def check_useless_nodes(workflow: "Workflow") -> list["Node"]:
167
+ """检查工作流中是否存在无用的节点。
168
+
169
+ 无用的节点定义:
170
+ 1. 节点非 output 类节点,并且节点的输出端口没有任何连线,说明该节点数据不会传给下一个节点或者显示出来。
171
+ """
172
+ useless_nodes = []
173
+ source_nodes = set(edge.source for edge in workflow.edges)
174
+
175
+ for node in workflow.nodes:
176
+ if hasattr(node, "category") and node.category == "outputs":
177
+ continue
178
+
179
+ if node.id not in source_nodes:
180
+ useless_nodes.append(node)
181
+
182
+ return useless_nodes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vectorvein
3
- Version: 0.2.54
3
+ Version: 0.2.56
4
4
  Summary: VectorVein Python SDK
5
5
  Author-Email: Anderson <andersonby@163.com>
6
6
  License: MIT
@@ -1,13 +1,13 @@
1
- vectorvein-0.2.54.dist-info/METADATA,sha256=b-MXVu9Jj3l07gGdq2ZyjImBBXXEZFTiCY826pJWT3U,4570
2
- vectorvein-0.2.54.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- vectorvein-0.2.54.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
1
+ vectorvein-0.2.56.dist-info/METADATA,sha256=B8IMfZGFGChU78LXUVrw4x9ArXDD4kYpuA3pmYDaGMI,4570
2
+ vectorvein-0.2.56.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ vectorvein-0.2.56.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
4
  vectorvein/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  vectorvein/api/__init__.py,sha256=lfY-XA46fgD2iIZTU0VYP8i07AwA03Egj4Qua0vUKrQ,738
6
6
  vectorvein/api/client.py,sha256=xF-leKDQzVyyy9FnIRaz0k4eElYW1XbbzeRLcpnyk90,33047
7
7
  vectorvein/api/exceptions.py,sha256=uS_PAdx0ksC0r3dgfSGWdbLMZm4qdLeWSSqCv1g3_Gc,772
8
8
  vectorvein/api/models.py,sha256=xtPWMsB0yIJI7i-gY4B6MtvXv0ZIXnoeKspmeInH6fU,1449
9
9
  vectorvein/chat_clients/__init__.py,sha256=UIytpIgwo8qkZpIyrHVxLYTyliUOTp4J7C4iHRjbtWE,23850
10
- vectorvein/chat_clients/anthropic_client.py,sha256=lvnE7IrtRehjiuXirhy09VH1NqiV5RFakfG8JAb9WNk,68362
10
+ vectorvein/chat_clients/anthropic_client.py,sha256=yrGb87h_hpxhK-O3Ir0L5yMHLimPgzaJqdW4AjzskHk,68924
11
11
  vectorvein/chat_clients/baichuan_client.py,sha256=CVMvpgjdrZGv0BWnTOBD-f2ufZ3wq3496wqukumsAr4,526
12
12
  vectorvein/chat_clients/base_client.py,sha256=p7s-G4Wh9MSpDKEfG8wuFAeWy5DGvj5Go31hqrpQPhM,38817
13
13
  vectorvein/chat_clients/deepseek_client.py,sha256=3qWu01NlJAP2N-Ff62d5-CZXZitlizE1fzb20LNetig,526
@@ -42,9 +42,9 @@ vectorvein/utilities/media_processing.py,sha256=7KtbLFzOYIn1e9QTN9G6C76NH8CBlV9k
42
42
  vectorvein/utilities/rate_limiter.py,sha256=dwolIUVw2wP83Odqpx0AAaE77de1GzxkYDGH4tM_u_4,10300
43
43
  vectorvein/utilities/retry.py,sha256=6KFS9R2HdhqM3_9jkjD4F36ZSpEx2YNFGOVlpOsUetM,2208
44
44
  vectorvein/workflow/graph/edge.py,sha256=1ckyyjCue_PLm7P1ItUfKOy6AKkemOpZ9m1WJ8UXIHQ,1072
45
- vectorvein/workflow/graph/node.py,sha256=6W9czon4JpWPcjwfN1B5-igEbifRsMemjGByZxa68RY,5047
45
+ vectorvein/workflow/graph/node.py,sha256=ZzOhl7pltPBR5gyyL2ZtYeByPxjDPnM179emliqGFi8,5192
46
46
  vectorvein/workflow/graph/port.py,sha256=_QpHCBGAu657VhYAh0Wzjri3ZZ8-WYJp99J465mqmwo,6492
47
- vectorvein/workflow/graph/workflow.py,sha256=W2ucuclAjtqjHOmvhLx1lWmFw1xMKMCKaYSV66NdDwo,5835
47
+ vectorvein/workflow/graph/workflow.py,sha256=lDF4LafX5dmzjQQqR-mFhqQPdF_AKodiHpM3MygoqyE,6086
48
48
  vectorvein/workflow/nodes/__init__.py,sha256=dWrWtL3q0Vsn-MLgJ7gNgLCrwZ5BrqjrN2QFPNeBMuc,3240
49
49
  vectorvein/workflow/nodes/audio_generation.py,sha256=ZRFZ_ycMTSJ2LKmekctagQdJYTl-3q4TNOIKETpS9AM,5870
50
50
  vectorvein/workflow/nodes/control_flows.py,sha256=l8CjFQlsGV3fNGM6SVzS1Kz361K1xDv1fGT7acuDXuU,6613
@@ -56,12 +56,12 @@ vectorvein/workflow/nodes/media_processing.py,sha256=0NuJCkcRY68yO9ZJ7xmXA8RXXxP
56
56
  vectorvein/workflow/nodes/output.py,sha256=_UQxiddHtGv2rkjhUFE-KDgrjnh0AGJQJyq9-4Aji5A,12567
57
57
  vectorvein/workflow/nodes/relational_db.py,sha256=zfzUhV25TpZGhkIzO18PmAT5xhcsJC4AXKy0zyA05w8,5408
58
58
  vectorvein/workflow/nodes/text_processing.py,sha256=MRo_-oaC65hbzMxm7TYoeiS3rgvqh9y_Rny5RCfoATE,8342
59
- vectorvein/workflow/nodes/tools.py,sha256=n3gF7r5FqNz4Xg0nXYo5kl4ai_g4tWSRHqfnGrKJAls,13850
59
+ vectorvein/workflow/nodes/tools.py,sha256=xaBmjJYtlUopoflIF7BR_l8RPVjYRPpeqa1dyQfXYas,13494
60
60
  vectorvein/workflow/nodes/triggers.py,sha256=BolH4X6S8HSuU2kwHmYKr-ozHbgKBmdZRcnXpK5EfGA,597
61
61
  vectorvein/workflow/nodes/vector_db.py,sha256=t6I17q6iR3yQreiDHpRrksMdWDPIvgqJs076z-7dlQQ,5712
62
62
  vectorvein/workflow/nodes/video_generation.py,sha256=qmdg-t_idpxq1veukd-jv_ChICMOoInKxprV9Z4Vi2w,4118
63
63
  vectorvein/workflow/nodes/web_crawlers.py,sha256=BhJBX1AZH7-22Gu95Ox4qJqmH5DU-m4dbUb5N5DTA-M,5559
64
- vectorvein/workflow/utils/check.py,sha256=sirsyg4io5BzgGM0VdEq5s9hyJk0z80Y_mNwCqMc6UI,6168
64
+ vectorvein/workflow/utils/check.py,sha256=N2eHyZZVCX_fFriK4pcdDShTCwaLNDngkC_247kF3-c,6836
65
65
  vectorvein/workflow/utils/json_to_code.py,sha256=F7dhDy8kGc8ndOeihGLRLGFGlquoxVlb02ENtxnQ0C8,5914
66
66
  vectorvein/workflow/utils/layout.py,sha256=j0bRD3uaXu40xCS6U6BGahBI8FrHa5MiF55GbTrZ1LM,4565
67
- vectorvein-0.2.54.dist-info/RECORD,,
67
+ vectorvein-0.2.56.dist-info/RECORD,,