vellum-ai 0.13.18__py3-none-any.whl → 0.13.19__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.
- vellum/client/core/client_wrapper.py +1 -1
- vellum/workflows/nodes/core/retry_node/node.py +13 -7
- vellum/workflows/outputs/base.py +11 -0
- {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.19.dist-info}/METADATA +1 -1
- {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.19.dist-info}/RECORD +12 -12
- vellum_cli/config.py +69 -11
- vellum_cli/tests/test_pull.py +42 -1
- vellum_ee/workflows/display/nodes/vellum/__init__.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +5 -0
- {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.19.dist-info}/LICENSE +0 -0
- {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.19.dist-info}/WHEEL +0 -0
- {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.19.dist-info}/entry_points.txt +0 -0
@@ -18,7 +18,7 @@ class BaseClientWrapper:
|
|
18
18
|
headers: typing.Dict[str, str] = {
|
19
19
|
"X-Fern-Language": "Python",
|
20
20
|
"X-Fern-SDK-Name": "vellum-ai",
|
21
|
-
"X-Fern-SDK-Version": "0.13.
|
21
|
+
"X-Fern-SDK-Version": "0.13.19",
|
22
22
|
}
|
23
23
|
headers["X_API_KEY"] = self.api_key
|
24
24
|
return headers
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import time
|
1
2
|
from typing import Callable, Generic, Optional, Type
|
2
3
|
|
3
4
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
@@ -17,11 +18,13 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
17
18
|
Used to retry a Subworkflow a specified number of times.
|
18
19
|
|
19
20
|
max_attempts: int - The maximum number of attempts to retry the Subworkflow
|
21
|
+
delay: float - The number of seconds to wait between retries
|
20
22
|
retry_on_error_code: Optional[VellumErrorCode] = None - The error code to retry on
|
21
23
|
subworkflow: Type["BaseWorkflow[SubworkflowInputs, BaseState]"] - The Subworkflow to execute
|
22
24
|
"""
|
23
25
|
|
24
26
|
max_attempts: int
|
27
|
+
delay: Optional[float] = None
|
25
28
|
retry_on_error_code: Optional[WorkflowErrorCode] = None
|
26
29
|
retry_on_condition: Optional[BaseDescriptor] = None
|
27
30
|
|
@@ -29,7 +32,9 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
29
32
|
attempt_number: int
|
30
33
|
|
31
34
|
def run(self) -> BaseNode.Outputs:
|
32
|
-
|
35
|
+
if self.max_attempts <= 0:
|
36
|
+
raise Exception("max_attempts must be greater than 0")
|
37
|
+
|
33
38
|
for index in range(self.max_attempts):
|
34
39
|
attempt_number = index + 1
|
35
40
|
context = WorkflowContext(vellum_client=self._context.vellum_client)
|
@@ -49,30 +54,29 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
49
54
|
|
50
55
|
return node_outputs
|
51
56
|
elif terminal_event.name == "workflow.execution.paused":
|
52
|
-
|
57
|
+
raise NodeException(
|
53
58
|
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
54
59
|
message=f"Subworkflow unexpectedly paused on attempt {attempt_number}",
|
55
60
|
)
|
56
|
-
break
|
57
61
|
elif self.retry_on_error_code and self.retry_on_error_code != terminal_event.error.code:
|
58
|
-
|
62
|
+
raise NodeException(
|
59
63
|
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
60
64
|
message=f"""Unexpected rejection on attempt {attempt_number}: {terminal_event.error.code.value}.
|
61
65
|
Message: {terminal_event.error.message}""",
|
62
66
|
)
|
63
|
-
break
|
64
67
|
elif self.retry_on_condition and not resolve_value(self.retry_on_condition, self.state):
|
65
|
-
|
68
|
+
raise NodeException(
|
66
69
|
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
67
70
|
message=f"""Rejection failed on attempt {attempt_number}: {terminal_event.error.code.value}.
|
68
71
|
Message: {terminal_event.error.message}""",
|
69
72
|
)
|
70
|
-
break
|
71
73
|
else:
|
72
74
|
last_exception = NodeException(
|
73
75
|
terminal_event.error.message,
|
74
76
|
code=terminal_event.error.code,
|
75
77
|
)
|
78
|
+
if self.delay:
|
79
|
+
time.sleep(self.delay)
|
76
80
|
|
77
81
|
raise last_exception
|
78
82
|
|
@@ -80,6 +84,7 @@ Message: {terminal_event.error.message}""",
|
|
80
84
|
def wrap(
|
81
85
|
cls,
|
82
86
|
max_attempts: int,
|
87
|
+
delay: Optional[float] = None,
|
83
88
|
retry_on_error_code: Optional[WorkflowErrorCode] = None,
|
84
89
|
retry_on_condition: Optional[BaseDescriptor] = None,
|
85
90
|
) -> Callable[..., Type["RetryNode"]]:
|
@@ -87,6 +92,7 @@ Message: {terminal_event.error.message}""",
|
|
87
92
|
cls,
|
88
93
|
attributes={
|
89
94
|
"max_attempts": max_attempts,
|
95
|
+
"delay": delay,
|
90
96
|
"retry_on_error_code": retry_on_error_code,
|
91
97
|
"retry_on_condition": retry_on_condition,
|
92
98
|
},
|
vellum/workflows/outputs/base.py
CHANGED
@@ -6,6 +6,8 @@ from pydantic_core import core_schema
|
|
6
6
|
|
7
7
|
from vellum.workflows.constants import UNDEF
|
8
8
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
9
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
10
|
+
from vellum.workflows.exceptions import NodeException
|
9
11
|
from vellum.workflows.references.output import OutputReference
|
10
12
|
from vellum.workflows.types.utils import get_class_attr_names, infer_types
|
11
13
|
|
@@ -183,6 +185,15 @@ class _BaseOutputsMeta(type):
|
|
183
185
|
|
184
186
|
class BaseOutputs(metaclass=_BaseOutputsMeta):
|
185
187
|
def __init__(self, **kwargs: Any) -> None:
|
188
|
+
declared_fields = {descriptor.name for descriptor in self.__class__}
|
189
|
+
provided_fields = set(kwargs.keys())
|
190
|
+
|
191
|
+
if not provided_fields.issubset(declared_fields):
|
192
|
+
raise NodeException(
|
193
|
+
message=f"Unexpected outputs: {provided_fields - declared_fields}",
|
194
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
195
|
+
)
|
196
|
+
|
186
197
|
for name, value in kwargs.items():
|
187
198
|
setattr(self, name, value)
|
188
199
|
|
@@ -2,7 +2,7 @@ vellum_cli/CONTRIBUTING.md,sha256=FtDC7BGxSeMnwCXAUssFsAIElXtmJE-O5Z7BpolcgvI,29
|
|
2
2
|
vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
|
3
3
|
vellum_cli/__init__.py,sha256=uEn2Nlt2Z0kBc79NcO4-rgOIE7H9nsMEEjWF6MLDlPo,10591
|
4
4
|
vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
|
5
|
-
vellum_cli/config.py,sha256=
|
5
|
+
vellum_cli/config.py,sha256=Bsb3mnvKvv3oOTcCuxpgC7lWPMqt6eJhgRA6VEE-vL4,9266
|
6
6
|
vellum_cli/image_push.py,sha256=SJwhwWJsLjwGNezNVd_oCVpFMfPsAB3dfLWmriZZUtw,4419
|
7
7
|
vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
|
8
8
|
vellum_cli/ping.py,sha256=lWyJw6sziXjyTopTYRdFF5hV-sYPVDdX0yVbG5fzcY4,585
|
@@ -13,7 +13,7 @@ vellum_cli/tests/conftest.py,sha256=AFYZryKA2qnUuCPBxBKmHLFoPiE0WhBFFej9tNwSHdc,
|
|
13
13
|
vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
|
14
14
|
vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
|
15
15
|
vellum_cli/tests/test_ping.py,sha256=QtbhYKMYn1DFnDyBij2mkQO32j9KOpZ5Pf0yek7k_Ao,1284
|
16
|
-
vellum_cli/tests/test_pull.py,sha256=
|
16
|
+
vellum_cli/tests/test_pull.py,sha256=JURmgGs5lSnpzefSx4K13eF2swv7O8OF86-4df81Zjo,25241
|
17
17
|
vellum_cli/tests/test_push.py,sha256=kcM8jp6vTRBwfk6cAC_tHa4Eliuy_zZ-X1DfIpSEIW0,18299
|
18
18
|
vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -28,7 +28,7 @@ vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
|
|
28
28
|
vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=QqR3Ly0RNrXwOeLdW5nERDFt0gRPf76n1bPES6o5UN4,1093
|
29
29
|
vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6mLhstQAvEACbGk,247
|
30
30
|
vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
|
31
|
-
vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=
|
31
|
+
vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=VHx6wSs9wuuiFlZpSQSd3mhECz4SUy2wEBuTSv--_As,1578
|
32
32
|
vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=hoV-cUtS6H9kmRQXHd2py95GRWI_dAnnaPwvlNBkDOQ,8571
|
33
33
|
vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=z00Z3L0d4PsUQo4S8FRDTtOFLtjdi17TJbatNVF4nM8,4288
|
34
34
|
vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=ybLIa4uclqVIy3VAQvI1ivg2tnK5Ug_1R5a69DFqL7E,11104
|
@@ -56,7 +56,7 @@ vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=h4bE187
|
|
56
56
|
vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
57
57
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
58
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=EenmEdBtHUFQ0OS-kE7Vboax3JnDdj-K4Qixt5oR0Po,2253
|
59
|
-
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=
|
59
|
+
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=KYdohS5pRgHM0DcUaK0tHa40f0gSvDKi2K5On0zNEU8,8305
|
60
60
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=yPXhdZxEDunNl5LL5Obb0jeO34djt7F-GiaTJhp_fmo,16742
|
61
61
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=-12ZkZb3f5gyoNASV2yeQtMo5HmNsVEo8nXwL6IC-I8,6261
|
62
62
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=6th6kCwzql6lddjkTQx4Jbvvs4ChqtJwctW-B4QuBhI,37352
|
@@ -113,7 +113,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
|
|
113
113
|
vellum/client/__init__.py,sha256=8nZt88C9SVwWanjLbIQMU3rzb32h5UZfFMBx3VPHB50,111887
|
114
114
|
vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
|
115
115
|
vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
|
116
|
-
vellum/client/core/client_wrapper.py,sha256=
|
116
|
+
vellum/client/core/client_wrapper.py,sha256=rnVChhyob3hO3iXrfL4gsCAYGAOdglXQdXoLL07FKLg,1869
|
117
117
|
vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
|
118
118
|
vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
|
119
119
|
vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
|
@@ -1323,7 +1323,7 @@ vellum/workflows/nodes/core/map_node/node.py,sha256=bjCVMAzkqJUvaLWVBObjskcutwLG
|
|
1323
1323
|
vellum/workflows/nodes/core/map_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1324
1324
|
vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=P7ImQyeLcK-aEJUYCX1gPQyuOxdxnSPpI3mNPpKQ62Y,1919
|
1325
1325
|
vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
|
1326
|
-
vellum/workflows/nodes/core/retry_node/node.py,sha256=
|
1326
|
+
vellum/workflows/nodes/core/retry_node/node.py,sha256=WD96o-eOj3dwEEe2nqxwBbmLTIyPRYB3Lk4T6XHRX74,4214
|
1327
1327
|
vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1328
1328
|
vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=RM_OHwxrHwyxvlQQBJPqVBxpedFuWQ9h2-Xa3kP75sc,4399
|
1329
1329
|
vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
|
@@ -1389,7 +1389,7 @@ vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha2
|
|
1389
1389
|
vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py,sha256=1EGeiaT-Zoo6pttQFKKBcdf3dmhAbjKGaErYD5FFwlc,10185
|
1390
1390
|
vellum/workflows/nodes/utils.py,sha256=T7krLipDSI007JkkH2zsfyQ-tfOBH4hyFzQ0YJKYpuw,4025
|
1391
1391
|
vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
|
1392
|
-
vellum/workflows/outputs/base.py,sha256=
|
1392
|
+
vellum/workflows/outputs/base.py,sha256=Wu48tqyQoxpflBUcwzKeZjSVp1LPKrBwuIvnx__9H90,8459
|
1393
1393
|
vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
|
1394
1394
|
vellum/workflows/ports/node_ports.py,sha256=g4A-8iUAvEJSkaWppbvzAR8XU02R9U-qLN4rP2Kq4Aw,2743
|
1395
1395
|
vellum/workflows/ports/port.py,sha256=rc3GB7dDQCUs0IbY08a92-31YzJHQgBeww13brSJ2Js,3172
|
@@ -1442,8 +1442,8 @@ vellum/workflows/vellum_client.py,sha256=ODrq_TSl-drX2aezXegf7pizpWDVJuTXH-j6528
|
|
1442
1442
|
vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
|
1443
1443
|
vellum/workflows/workflows/base.py,sha256=k0kUWWko4fHyCqLSU_1cBK_pXZpl9MXekWiG-bdOAo0,18353
|
1444
1444
|
vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
|
1445
|
-
vellum_ai-0.13.
|
1446
|
-
vellum_ai-0.13.
|
1447
|
-
vellum_ai-0.13.
|
1448
|
-
vellum_ai-0.13.
|
1449
|
-
vellum_ai-0.13.
|
1445
|
+
vellum_ai-0.13.19.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
|
1446
|
+
vellum_ai-0.13.19.dist-info/METADATA,sha256=NzphP-OKn-7lL7hjSD1e1eT709mTax682Cs85AT0O_4,5335
|
1447
|
+
vellum_ai-0.13.19.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1448
|
+
vellum_ai-0.13.19.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
|
1449
|
+
vellum_ai-0.13.19.dist-info/RECORD,,
|
vellum_cli/config.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
from collections import defaultdict
|
1
2
|
from dataclasses import field
|
2
3
|
import json
|
3
4
|
import os
|
@@ -88,6 +89,50 @@ class WorkflowConfig(UniversalBaseModel):
|
|
88
89
|
)
|
89
90
|
|
90
91
|
|
92
|
+
def merge_workflows_by_sandbox_id(
|
93
|
+
workflows: List[WorkflowConfig], other_workflows: List[WorkflowConfig]
|
94
|
+
) -> List[WorkflowConfig]:
|
95
|
+
merged_workflows: List[WorkflowConfig] = []
|
96
|
+
for self_workflow in workflows:
|
97
|
+
if self_workflow.workflow_sandbox_id is None:
|
98
|
+
# If the user defines a workflow in the pyproject.toml without a sandbox_id,
|
99
|
+
# we merge the workflow with one of the ones in the lockfile.
|
100
|
+
other_workflow = next(
|
101
|
+
(
|
102
|
+
other_workflow
|
103
|
+
for other_workflow in other_workflows
|
104
|
+
if self_workflow.workspace == other_workflow.workspace
|
105
|
+
),
|
106
|
+
None,
|
107
|
+
)
|
108
|
+
if other_workflow is not None:
|
109
|
+
merged_workflows.append(self_workflow.merge(other_workflow))
|
110
|
+
else:
|
111
|
+
merged_workflows.append(self_workflow)
|
112
|
+
else:
|
113
|
+
# If the user defines a workflow in the pyproject.toml with a sandbox_id,
|
114
|
+
# we merge the workflow with one of the ones in the lockfile with the same sandbox_id.
|
115
|
+
other_workflow = next(
|
116
|
+
(
|
117
|
+
other_workflow
|
118
|
+
for other_workflow in other_workflows
|
119
|
+
if self_workflow.workflow_sandbox_id == other_workflow.workflow_sandbox_id
|
120
|
+
),
|
121
|
+
None,
|
122
|
+
)
|
123
|
+
if other_workflow is not None:
|
124
|
+
merged_workflows.append(self_workflow.merge(other_workflow))
|
125
|
+
else:
|
126
|
+
merged_workflows.append(self_workflow)
|
127
|
+
|
128
|
+
workflow_sandbox_ids_so_far = {workflow.workflow_sandbox_id for workflow in merged_workflows}
|
129
|
+
for other_workflow in other_workflows:
|
130
|
+
if other_workflow.workflow_sandbox_id not in workflow_sandbox_ids_so_far:
|
131
|
+
merged_workflows.append(other_workflow)
|
132
|
+
|
133
|
+
return merged_workflows
|
134
|
+
|
135
|
+
|
91
136
|
class VellumCliConfig(UniversalBaseModel):
|
92
137
|
version: Literal["1.0"] = "1.0"
|
93
138
|
workflows: List[WorkflowConfig] = field(default_factory=list)
|
@@ -97,24 +142,31 @@ class VellumCliConfig(UniversalBaseModel):
|
|
97
142
|
lockfile_path = os.path.join(os.getcwd(), LOCKFILE_PATH)
|
98
143
|
with open(lockfile_path, "w") as f:
|
99
144
|
json.dump(self.model_dump(), f, indent=2, cls=DefaultStateEncoder)
|
145
|
+
# Makes sure the file ends with a newline, consistent with most autoformatters
|
146
|
+
f.write("\n")
|
100
147
|
|
101
148
|
def merge(self, other: "VellumCliConfig") -> "VellumCliConfig":
|
102
149
|
if other.version != self.version:
|
103
150
|
raise ValueError("Lockfile version mismatch")
|
104
151
|
|
105
|
-
|
106
|
-
|
107
|
-
all_modules = sorted(set(
|
152
|
+
self_workflows_by_module = self.get_workflows_by_module_mapping()
|
153
|
+
other_workflows_by_module = other.get_workflows_by_module_mapping()
|
154
|
+
all_modules = sorted(set(self_workflows_by_module.keys()).union(set(other_workflows_by_module.keys())))
|
108
155
|
merged_workflows = []
|
109
156
|
for module in all_modules:
|
110
|
-
|
111
|
-
|
112
|
-
if
|
113
|
-
merged_workflows.
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
157
|
+
self_workflows = self_workflows_by_module.get(module)
|
158
|
+
other_workflows = other_workflows_by_module.get(module)
|
159
|
+
if self_workflows and other_workflows:
|
160
|
+
merged_workflows.extend(
|
161
|
+
merge_workflows_by_sandbox_id(
|
162
|
+
self_workflows,
|
163
|
+
other_workflows,
|
164
|
+
)
|
165
|
+
)
|
166
|
+
elif self_workflows:
|
167
|
+
merged_workflows.extend(self_workflows)
|
168
|
+
elif other_workflows:
|
169
|
+
merged_workflows.extend(other_workflows)
|
118
170
|
|
119
171
|
self_workspace_by_name = {workspace.name: workspace for workspace in self.workspaces}
|
120
172
|
other_workspace_by_name = {workspace.name: workspace for workspace in other.workspaces}
|
@@ -132,6 +184,12 @@ class VellumCliConfig(UniversalBaseModel):
|
|
132
184
|
|
133
185
|
return VellumCliConfig(workflows=merged_workflows, workspaces=merged_workspaces, version=self.version)
|
134
186
|
|
187
|
+
def get_workflows_by_module_mapping(self) -> Dict[str, List[WorkflowConfig]]:
|
188
|
+
workflows_by_module = defaultdict(list)
|
189
|
+
for workflow in self.workflows:
|
190
|
+
workflows_by_module[workflow.module].append(workflow)
|
191
|
+
return workflows_by_module
|
192
|
+
|
135
193
|
|
136
194
|
def load_vellum_cli_config(root_dir: Optional[str] = None) -> VellumCliConfig:
|
137
195
|
if root_dir is None:
|
vellum_cli/tests/test_pull.py
CHANGED
@@ -428,7 +428,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
428
428
|
"workflows": [
|
429
429
|
{
|
430
430
|
"module": module,
|
431
|
-
"workflow_sandbox_id":
|
431
|
+
"workflow_sandbox_id": workflow_sandbox_id,
|
432
432
|
"ignore": "tests/*",
|
433
433
|
"deployments": [
|
434
434
|
{
|
@@ -646,3 +646,44 @@ def test_pull__module_not_in_config(vellum_client, mock_module):
|
|
646
646
|
"workspace": "default",
|
647
647
|
}
|
648
648
|
]
|
649
|
+
|
650
|
+
|
651
|
+
def test_pull__multiple_instances_of_same_module__keep_when_pulling_another_module(vellum_client, mock_module):
|
652
|
+
# GIVEN a module on the user's filesystem
|
653
|
+
module = mock_module.module
|
654
|
+
temp_dir = mock_module.temp_dir
|
655
|
+
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
656
|
+
|
657
|
+
# AND the vellum lock file has two instances of some other module
|
658
|
+
lock_data = {
|
659
|
+
"workflows": [
|
660
|
+
{
|
661
|
+
"module": "some_other_module",
|
662
|
+
"workflow_sandbox_id": str(uuid4()),
|
663
|
+
"workspace": "default",
|
664
|
+
},
|
665
|
+
{
|
666
|
+
"module": "some_other_module",
|
667
|
+
"workflow_sandbox_id": str(uuid4()),
|
668
|
+
"workspace": "other",
|
669
|
+
},
|
670
|
+
]
|
671
|
+
}
|
672
|
+
lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
673
|
+
with open(lock_json, "w") as f:
|
674
|
+
json.dump(lock_data, f)
|
675
|
+
|
676
|
+
# AND the workflow pull API call returns a zip file
|
677
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
678
|
+
|
679
|
+
# WHEN the user runs the pull command on the new module
|
680
|
+
runner = CliRunner()
|
681
|
+
result = runner.invoke(cli_main, ["workflows", "pull", module, "--workflow-sandbox-id", workflow_sandbox_id])
|
682
|
+
|
683
|
+
# THEN the command returns successfully
|
684
|
+
assert result.exit_code == 0, (result.output, result.exception)
|
685
|
+
|
686
|
+
# AND the lockfile should have all three entries
|
687
|
+
with open(lock_json) as f:
|
688
|
+
lock_data = json.load(f)
|
689
|
+
assert len(lock_data["workflows"]) == 3
|
@@ -18,7 +18,6 @@ from .try_node import BaseTryNodeDisplay
|
|
18
18
|
|
19
19
|
# All node display classes must be imported here to be registered in BaseNodeDisplay's node display registry
|
20
20
|
__all__ = [
|
21
|
-
"BaseRetryNodeDisplay",
|
22
21
|
"BaseAPINodeDisplay",
|
23
22
|
"BaseCodeExecutionNodeDisplay",
|
24
23
|
"BaseConditionalNodeDisplay",
|
@@ -31,6 +30,7 @@ __all__ = [
|
|
31
30
|
"BaseMergeNodeDisplay",
|
32
31
|
"BaseNoteNodeDisplay",
|
33
32
|
"BasePromptDeploymentNodeDisplay",
|
33
|
+
"BaseRetryNodeDisplay",
|
34
34
|
"BaseSearchNodeDisplay",
|
35
35
|
"BaseSubworkflowDeploymentNodeDisplay",
|
36
36
|
"BaseTemplatingNodeDisplay",
|
@@ -80,6 +80,11 @@ def test_serialize_node__retry(serialize_node):
|
|
80
80
|
"module": ["vellum", "workflows", "nodes", "core", "retry_node", "node"],
|
81
81
|
},
|
82
82
|
"attributes": [
|
83
|
+
{
|
84
|
+
"id": "8a07dc58-3fed-41d4-8ca6-31ee0bb86c61",
|
85
|
+
"name": "delay",
|
86
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
87
|
+
},
|
83
88
|
{
|
84
89
|
"id": "f388e93b-8c68-4f54-8577-bbd0c9091557",
|
85
90
|
"name": "max_attempts",
|
File without changes
|
File without changes
|
File without changes
|