vectorvein 0.2.54__tar.gz → 0.2.56__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 (66) hide show
  1. {vectorvein-0.2.54 → vectorvein-0.2.56}/PKG-INFO +1 -1
  2. {vectorvein-0.2.54 → vectorvein-0.2.56}/pyproject.toml +1 -1
  3. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/anthropic_client.py +12 -2
  4. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/graph/node.py +6 -0
  5. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/graph/workflow.py +8 -3
  6. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/tools.py +356 -356
  7. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/utils/check.py +25 -4
  8. {vectorvein-0.2.54 → vectorvein-0.2.56}/README.md +0 -0
  9. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/__init__.py +0 -0
  10. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/api/__init__.py +0 -0
  11. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/api/client.py +0 -0
  12. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/api/exceptions.py +0 -0
  13. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/api/models.py +0 -0
  14. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/__init__.py +0 -0
  15. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/baichuan_client.py +0 -0
  16. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/base_client.py +0 -0
  17. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/deepseek_client.py +0 -0
  18. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/ernie_client.py +0 -0
  19. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/gemini_client.py +0 -0
  20. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/groq_client.py +0 -0
  21. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/local_client.py +0 -0
  22. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/minimax_client.py +0 -0
  23. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/mistral_client.py +0 -0
  24. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/moonshot_client.py +0 -0
  25. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/openai_client.py +0 -0
  26. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/openai_compatible_client.py +0 -0
  27. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/py.typed +0 -0
  28. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/qwen_client.py +0 -0
  29. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/stepfun_client.py +0 -0
  30. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/utils.py +0 -0
  31. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/xai_client.py +0 -0
  32. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/yi_client.py +0 -0
  33. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/chat_clients/zhipuai_client.py +0 -0
  34. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/py.typed +0 -0
  35. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/server/token_server.py +0 -0
  36. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/settings/__init__.py +0 -0
  37. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/settings/py.typed +0 -0
  38. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/types/__init__.py +0 -0
  39. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/types/defaults.py +0 -0
  40. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/types/enums.py +0 -0
  41. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/types/exception.py +0 -0
  42. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/types/llm_parameters.py +0 -0
  43. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/types/py.typed +0 -0
  44. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/types/settings.py +0 -0
  45. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/utilities/media_processing.py +0 -0
  46. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/utilities/rate_limiter.py +0 -0
  47. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/utilities/retry.py +0 -0
  48. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/graph/edge.py +0 -0
  49. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/graph/port.py +0 -0
  50. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/__init__.py +0 -0
  51. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/audio_generation.py +0 -0
  52. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/control_flows.py +0 -0
  53. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/file_processing.py +0 -0
  54. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/image_generation.py +0 -0
  55. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/llms.py +0 -0
  56. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/media_editing.py +0 -0
  57. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/media_processing.py +0 -0
  58. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/output.py +0 -0
  59. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/relational_db.py +0 -0
  60. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/text_processing.py +0 -0
  61. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/triggers.py +0 -0
  62. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/vector_db.py +0 -0
  63. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/video_generation.py +0 -0
  64. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/nodes/web_crawlers.py +0 -0
  65. {vectorvein-0.2.54 → vectorvein-0.2.56}/src/vectorvein/workflow/utils/json_to_code.py +0 -0
  66. {vectorvein-0.2.54 → vectorvein-0.2.56}/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.54
3
+ Version: 0.2.56
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.54"
20
+ version = "0.2.56"
21
21
 
22
22
  [project.license]
23
23
  text = "MIT"
@@ -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
File without changes