vellum-ai 0.14.7__py3-none-any.whl → 0.14.8__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/__init__.py +2 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/types/__init__.py +2 -0
- vellum/client/types/document_prompt_block.py +29 -0
- vellum/client/types/prompt_block.py +2 -0
- vellum/types/document_prompt_block.py +3 -0
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +33 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.8.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.8.dist-info}/RECORD +24 -22
- vellum_cli/__init__.py +9 -2
- vellum_cli/config.py +1 -0
- vellum_cli/init.py +6 -2
- vellum_cli/pull.py +1 -0
- vellum_cli/tests/test_init.py +194 -76
- vellum_cli/tests/test_pull.py +8 -0
- vellum_cli/tests/test_push.py +1 -0
- vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +83 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +118 -3
- vellum_ee/workflows/display/workflows/base_workflow_display.py +46 -13
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +12 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.8.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.8.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.8.dist-info}/entry_points.txt +0 -0
vellum/__init__.py
CHANGED
@@ -83,6 +83,7 @@ from .types import (
|
|
83
83
|
DocumentIndexIndexingConfigRequest,
|
84
84
|
DocumentIndexRead,
|
85
85
|
DocumentProcessingState,
|
86
|
+
DocumentPromptBlock,
|
86
87
|
DocumentRead,
|
87
88
|
DocumentStatus,
|
88
89
|
DocumentVellumValue,
|
@@ -623,6 +624,7 @@ __all__ = [
|
|
623
624
|
"DocumentIndexRead",
|
624
625
|
"DocumentIndexesListRequestStatus",
|
625
626
|
"DocumentProcessingState",
|
627
|
+
"DocumentPromptBlock",
|
626
628
|
"DocumentRead",
|
627
629
|
"DocumentStatus",
|
628
630
|
"DocumentVellumValue",
|
@@ -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.14.
|
21
|
+
"X-Fern-SDK-Version": "0.14.8",
|
22
22
|
}
|
23
23
|
headers["X_API_KEY"] = self.api_key
|
24
24
|
return headers
|
vellum/client/types/__init__.py
CHANGED
@@ -87,6 +87,7 @@ from .document_index_indexing_config import DocumentIndexIndexingConfig
|
|
87
87
|
from .document_index_indexing_config_request import DocumentIndexIndexingConfigRequest
|
88
88
|
from .document_index_read import DocumentIndexRead
|
89
89
|
from .document_processing_state import DocumentProcessingState
|
90
|
+
from .document_prompt_block import DocumentPromptBlock
|
90
91
|
from .document_read import DocumentRead
|
91
92
|
from .document_status import DocumentStatus
|
92
93
|
from .document_vellum_value import DocumentVellumValue
|
@@ -611,6 +612,7 @@ __all__ = [
|
|
611
612
|
"DocumentIndexIndexingConfigRequest",
|
612
613
|
"DocumentIndexRead",
|
613
614
|
"DocumentProcessingState",
|
615
|
+
"DocumentPromptBlock",
|
614
616
|
"DocumentRead",
|
615
617
|
"DocumentStatus",
|
616
618
|
"DocumentVellumValue",
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
from ..core.pydantic_utilities import UniversalBaseModel
|
4
|
+
import typing
|
5
|
+
from .prompt_block_state import PromptBlockState
|
6
|
+
from .ephemeral_prompt_cache_config import EphemeralPromptCacheConfig
|
7
|
+
from ..core.pydantic_utilities import IS_PYDANTIC_V2
|
8
|
+
import pydantic
|
9
|
+
|
10
|
+
|
11
|
+
class DocumentPromptBlock(UniversalBaseModel):
|
12
|
+
"""
|
13
|
+
A block that represents a document in a prompt template.
|
14
|
+
"""
|
15
|
+
|
16
|
+
block_type: typing.Literal["DOCUMENT"] = "DOCUMENT"
|
17
|
+
state: typing.Optional[PromptBlockState] = None
|
18
|
+
cache_config: typing.Optional[EphemeralPromptCacheConfig] = None
|
19
|
+
src: str
|
20
|
+
metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None
|
21
|
+
|
22
|
+
if IS_PYDANTIC_V2:
|
23
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
24
|
+
else:
|
25
|
+
|
26
|
+
class Config:
|
27
|
+
frozen = True
|
28
|
+
smart_union = True
|
29
|
+
extra = pydantic.Extra.allow
|
@@ -8,6 +8,7 @@ from .rich_text_prompt_block import RichTextPromptBlock
|
|
8
8
|
from .audio_prompt_block import AudioPromptBlock
|
9
9
|
from .function_call_prompt_block import FunctionCallPromptBlock
|
10
10
|
from .image_prompt_block import ImagePromptBlock
|
11
|
+
from .document_prompt_block import DocumentPromptBlock
|
11
12
|
import typing
|
12
13
|
|
13
14
|
if typing.TYPE_CHECKING:
|
@@ -20,4 +21,5 @@ PromptBlock = typing.Union[
|
|
20
21
|
AudioPromptBlock,
|
21
22
|
FunctionCallPromptBlock,
|
22
23
|
ImagePromptBlock,
|
24
|
+
DocumentPromptBlock,
|
23
25
|
]
|
@@ -1,5 +1,9 @@
|
|
1
|
+
import pytest
|
2
|
+
|
1
3
|
from vellum import ExecuteApiResponse, VellumSecret as ClientVellumSecret
|
4
|
+
from vellum.client.core.api_error import ApiError
|
2
5
|
from vellum.workflows.constants import APIRequestMethod, AuthorizationType
|
6
|
+
from vellum.workflows.exceptions import NodeException
|
3
7
|
from vellum.workflows.nodes import APINode
|
4
8
|
from vellum.workflows.state import BaseState
|
5
9
|
from vellum.workflows.types.core import VellumSecret
|
@@ -32,3 +36,32 @@ def test_run_workflow__secrets(vellum_client):
|
|
32
36
|
bearer_token = vellum_client.execute_api.call_args.kwargs["bearer_token"]
|
33
37
|
assert bearer_token == ClientVellumSecret(name="secret")
|
34
38
|
assert terminal.headers == {"X-Response-Header": "bar"}
|
39
|
+
|
40
|
+
|
41
|
+
def test_api_node_raises_error_when_api_call_fails(vellum_client):
|
42
|
+
# Mock the vellum_client to raise an ApiError
|
43
|
+
vellum_client.execute_api.side_effect = ApiError(status_code=400, body="API Error")
|
44
|
+
|
45
|
+
class SimpleAPINode(APINode):
|
46
|
+
method = APIRequestMethod.GET
|
47
|
+
authorization_type = AuthorizationType.BEARER_TOKEN
|
48
|
+
url = "https://api.vellum.ai"
|
49
|
+
body = {
|
50
|
+
"key": "value",
|
51
|
+
}
|
52
|
+
headers = {
|
53
|
+
"X-Test-Header": "foo",
|
54
|
+
}
|
55
|
+
bearer_token_value = VellumSecret(name="api_key")
|
56
|
+
|
57
|
+
node = SimpleAPINode(state=BaseState())
|
58
|
+
|
59
|
+
# Assert that the NodeException is raised
|
60
|
+
with pytest.raises(NodeException) as excinfo:
|
61
|
+
node.run()
|
62
|
+
|
63
|
+
# Verify that the exception contains some error message
|
64
|
+
assert "Failed to prepare HTTP request" in str(excinfo.value)
|
65
|
+
|
66
|
+
# Verify the vellum_client was called
|
67
|
+
assert vellum_client.execute_api.call_count == 1
|
@@ -89,7 +89,7 @@ class BaseAPINode(BaseNode, Generic[StateType]):
|
|
89
89
|
url=url, method=method.value, body=data, headers=headers, bearer_token=client_vellum_secret
|
90
90
|
)
|
91
91
|
except ApiError as e:
|
92
|
-
NodeException(f"Failed to prepare HTTP request: {e}", code=WorkflowErrorCode.NODE_EXECUTION)
|
92
|
+
raise NodeException(f"Failed to prepare HTTP request: {e}", code=WorkflowErrorCode.NODE_EXECUTION)
|
93
93
|
|
94
94
|
return self.Outputs(
|
95
95
|
json=vellum_response.json_,
|
@@ -1,22 +1,22 @@
|
|
1
1
|
vellum_cli/CONTRIBUTING.md,sha256=FtDC7BGxSeMnwCXAUssFsAIElXtmJE-O5Z7BpolcgvI,2935
|
2
2
|
vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
|
3
|
-
vellum_cli/__init__.py,sha256=
|
3
|
+
vellum_cli/__init__.py,sha256=7aO9XFnaEVRiVshn86cFudebFUccT-gV8xIARJWqKYo,12257
|
4
4
|
vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
|
5
|
-
vellum_cli/config.py,sha256=
|
5
|
+
vellum_cli/config.py,sha256=aKnhvM5B8QdPA4cQC5Sqg7ImP-vNcVdSkZmk_OBpQTw,9309
|
6
6
|
vellum_cli/image_push.py,sha256=SJwhwWJsLjwGNezNVd_oCVpFMfPsAB3dfLWmriZZUtw,4419
|
7
|
-
vellum_cli/init.py,sha256=
|
7
|
+
vellum_cli/init.py,sha256=WpnMXPItPmh0f0bBGIer3p-e5gu8DUGwSArT_FuoMEw,5093
|
8
8
|
vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
|
9
9
|
vellum_cli/ping.py,sha256=lWyJw6sziXjyTopTYRdFF5hV-sYPVDdX0yVbG5fzcY4,585
|
10
|
-
vellum_cli/pull.py,sha256=
|
10
|
+
vellum_cli/pull.py,sha256=XrlJqImcqZcr6SRGqJ4x3yyvc_0LHDejBcfeVRpY1mY,9169
|
11
11
|
vellum_cli/push.py,sha256=xjTNbLwOVFNU3kpBrm56Bk5QkSRrJ9z86qceghCzfIA,9655
|
12
12
|
vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
vellum_cli/tests/conftest.py,sha256=AFYZryKA2qnUuCPBxBKmHLFoPiE0WhBFFej9tNwSHdc,1526
|
14
14
|
vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
|
15
|
-
vellum_cli/tests/test_init.py,sha256=
|
15
|
+
vellum_cli/tests/test_init.py,sha256=8UOc_ThfouR4ja5cCl_URuLk7ohr9JXfCnG4yka1OUQ,18754
|
16
16
|
vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
|
17
17
|
vellum_cli/tests/test_ping.py,sha256=QtbhYKMYn1DFnDyBij2mkQO32j9KOpZ5Pf0yek7k_Ao,1284
|
18
|
-
vellum_cli/tests/test_pull.py,sha256=
|
19
|
-
vellum_cli/tests/test_push.py,sha256=
|
18
|
+
vellum_cli/tests/test_pull.py,sha256=09BkkBoFvqJXIFRxdCu-_a6CE6FtGzqXkXMPaKlcvwE,30178
|
19
|
+
vellum_cli/tests/test_push.py,sha256=zDv_Q1hbXtLwmTJDPRAvwDjbuHC09uNRYOy4FQujUow,23476
|
20
20
|
vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
21
|
vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -56,11 +56,11 @@ vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=4YUaTeD_OWF-
|
|
56
56
|
vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=HBfGz4yt9GlmMW9JxzaCacPnHBDNIeXE8Jhqr9DqLLw,6191
|
57
57
|
vellum_ee/workflows/display/nodes/vellum/utils.py,sha256=F_0BrlSszllK_BhryPbojIleLq2dGXOfQD1rVp3fNFg,4733
|
58
58
|
vellum_ee/workflows/display/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
|
-
vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=
|
59
|
+
vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=yfTwpPgOzxJBrUz4cb-T8QQf8lF3TYm-Of40usUNOnc,7494
|
60
60
|
vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
61
61
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
62
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=A1-tIpC5KIKG9JA_rkd1nLS8zUG3Kb4QiVdvb3boFxE,2509
|
63
|
-
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=
|
63
|
+
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=9bIAEXXZQDqsUrDJqmHEeWYiZsYkVTQ4jBY-dPFVXEc,15054
|
64
64
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=1cszL6N6FNGVm61MOa7AEiHnF0QjZWqDQuPOp4yiG94,18277
|
65
65
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=-12ZkZb3f5gyoNASV2yeQtMo5HmNsVEo8nXwL6IC-I8,6261
|
66
66
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=6th6kCwzql6lddjkTQx4Jbvvs4ChqtJwctW-B4QuBhI,37352
|
@@ -89,10 +89,10 @@ vellum_ee/workflows/display/utils/expressions.py,sha256=9FpOslDI-RCR5m4TgAu9KCHh
|
|
89
89
|
vellum_ee/workflows/display/utils/vellum.py,sha256=UjK_RxnSEmlIu9klGCPWU5RAQBmgZ7cRbRdgxaTbubE,8081
|
90
90
|
vellum_ee/workflows/display/vellum.py,sha256=7mqQaKZPPrLMcXSAQkPIxCy5x8HkKs5PbCu3GRaC2o8,8507
|
91
91
|
vellum_ee/workflows/display/workflows/__init__.py,sha256=kapXsC67VJcgSuiBMa86FdePG5A9kMB5Pi4Uy1O2ob4,207
|
92
|
-
vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=
|
92
|
+
vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=hTX1PQGSpuEiuAjlonyxE9V48UzTy4ReczX-dn8oPeY,18655
|
93
93
|
vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=kp0u8LN_2IwshLrhMImhpZx1hRyAcD5gXY-kDuuaGMQ,1269
|
94
94
|
vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=_yB3-u7_bWdD4lUBWpRdWztJmJL-DXkkZaw9Vy9HH6g,3245
|
95
|
-
vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=
|
95
|
+
vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=mbAzCpswOek34ITeTkesbVreCXpulj4NFjIg3RcdVZ8,18243
|
96
96
|
vellum_ee/workflows/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
97
97
|
vellum_ee/workflows/server/virtual_file_loader.py,sha256=X_DdNK7MfyOjKWekk6YQpOSCT6klKcdjT6nVJcBH1sM,1481
|
98
98
|
vellum_ee/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -117,12 +117,12 @@ vellum_ee/workflows/tests/local_workflow/workflow.py,sha256=A4qOzOPNwePYxWbcAgIP
|
|
117
117
|
vellum_ee/workflows/tests/test_display_meta.py,sha256=pzdqND4KLWs7EUIbpXuqgso7BIRpoUsO3T_bgeENs0Q,2205
|
118
118
|
vellum_ee/workflows/tests/test_server.py,sha256=SvKUrUPmOf3sIInXcFjETekql60npb4cAn1GPbF0bPs,391
|
119
119
|
vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
|
120
|
-
vellum/__init__.py,sha256=
|
120
|
+
vellum/__init__.py,sha256=a_aM1_A04XGma4MAIDNeBF9BKzWbiQaVVMRzImHuxjA,36438
|
121
121
|
vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
|
122
122
|
vellum/client/__init__.py,sha256=tKtdM1_GqmGq1gpi9ydWD_T-MM7fPn8QdHh8ww19cNI,117564
|
123
123
|
vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
|
124
124
|
vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
|
125
|
-
vellum/client/core/client_wrapper.py,sha256=
|
125
|
+
vellum/client/core/client_wrapper.py,sha256=NOBkPB9txdePCb8-MgjYVJwNHQWPG9yrRX2B7sxaT-o,1868
|
126
126
|
vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
|
127
127
|
vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
|
128
128
|
vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
|
@@ -187,7 +187,7 @@ vellum/client/resources/workspace_secrets/__init__.py,sha256=FTtvy8EDg9nNNg9WCat
|
|
187
187
|
vellum/client/resources/workspace_secrets/client.py,sha256=h7UzXLyTttPq1t-JZGMg1BWxypxJvBGUdqg7KGT7MK4,8027
|
188
188
|
vellum/client/resources/workspaces/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
|
189
189
|
vellum/client/resources/workspaces/client.py,sha256=RthwzN1o-Jxwg5yyNNodavFyNUSxfLoTv26w3mRR5g8,3595
|
190
|
-
vellum/client/types/__init__.py,sha256=
|
190
|
+
vellum/client/types/__init__.py,sha256=_1pPNxQxjYSBB4L-j9HM1CHRaOEQNZa6XekVrKrseqg,54850
|
191
191
|
vellum/client/types/ad_hoc_execute_prompt_event.py,sha256=bCjujA2XsOgyF3bRZbcEqV2rOIymRgsLoIRtZpB14xg,607
|
192
192
|
vellum/client/types/ad_hoc_expand_meta.py,sha256=1gv-NCsy_6xBYupLvZH979yf2VMdxAU-l0y0ynMKZaw,1331
|
193
193
|
vellum/client/types/ad_hoc_fulfilled_prompt_execution_meta.py,sha256=Bfvf1d_dkmshxRACVM5vcxbH_7AQY23RmrrnPc0ytYY,939
|
@@ -267,6 +267,7 @@ vellum/client/types/document_index_indexing_config.py,sha256=xL1pCzUOkw5sSie1OrB
|
|
267
267
|
vellum/client/types/document_index_indexing_config_request.py,sha256=Wt-ys1o_acHNyLU0c1laG2PVT7rgCfwO54f5nudAxk4,832
|
268
268
|
vellum/client/types/document_index_read.py,sha256=cXL115A4h-TFiGc29tAkXb1pkuK0RzIquyOu1Pv7Jug,1469
|
269
269
|
vellum/client/types/document_processing_state.py,sha256=ISlurj7jQzwHzxPzDZTqeAIgSIIGMBBPgcOSoe04pTU,211
|
270
|
+
vellum/client/types/document_prompt_block.py,sha256=sgFxN48PILFuuF2KUIwks6PbJ3XH6sCE_8ydLEE_doU,1019
|
270
271
|
vellum/client/types/document_read.py,sha256=6nwEvVvVe-6y2vtPNYB7KtcFoaydH2ow-WhCmCAvMQ8,1713
|
271
272
|
vellum/client/types/document_status.py,sha256=GD_TSoFmZUBJnPl-chAmaQFzQ2_TYO3PSqi3-9QfEHE,122
|
272
273
|
vellum/client/types/document_vellum_value.py,sha256=a8WQhyntwy80iN9j8L9F5v6Jmq1L4j0ETJo9c9VGabs,768
|
@@ -453,7 +454,7 @@ vellum/client/types/pdf_search_result_meta_source_request.py,sha256=nUhaD2Kw1paG
|
|
453
454
|
vellum/client/types/plain_text_prompt_block.py,sha256=cqEN-B4mcvMw_9lBN7FQG8pk9b5LBJ9xpM6PTgkGiqs,930
|
454
455
|
vellum/client/types/price.py,sha256=ewzXDBVLaleuXMVQ-gQ3G1Nl5J2OWOVEMEFfnQIpiTk,610
|
455
456
|
vellum/client/types/processing_failure_reason_enum.py,sha256=R_KIW7TcQejhc-vLhtNf9SdkYADgoZCn4ch4_RRIvsI,195
|
456
|
-
vellum/client/types/prompt_block.py,sha256=
|
457
|
+
vellum/client/types/prompt_block.py,sha256=quAME4X2doCO_DQ-U7v0Py-ZZy1Z5qypVVq2fXuazpw,827
|
457
458
|
vellum/client/types/prompt_block_state.py,sha256=BRAzTYARoSU36IVZGWMeeqhl5fgFMXCyhJ8rCbfB-f0,163
|
458
459
|
vellum/client/types/prompt_deployment_expand_meta_request.py,sha256=agsiAaHB6lDoZPlnfJ2nmhB4Ud4EiJJTX05YmduyCPo,1910
|
459
460
|
vellum/client/types/prompt_deployment_input_request.py,sha256=KrT4-Ew2VvTWXEkYQz2oyHn5EDOgrMW7FzRFaPH3ARg,353
|
@@ -857,6 +858,7 @@ vellum/types/document_index_indexing_config.py,sha256=q-thOinZy-BBQsKXZcw2jRu3cA
|
|
857
858
|
vellum/types/document_index_indexing_config_request.py,sha256=m9fL0NlibO4iTqVaJM90VFUQNvV9aG5b57NNh0hvgU0,176
|
858
859
|
vellum/types/document_index_read.py,sha256=7053CeFkTD9X5MRrVpiCRwKHGAQNtzNd6LnVCDePsM0,157
|
859
860
|
vellum/types/document_processing_state.py,sha256=7EKGnlG1AFm62N_xxeWVrbRVfSrNeJ_3rbnZAlle1nQ,163
|
861
|
+
vellum/types/document_prompt_block.py,sha256=ioBoNvFp4GpAuQhiu6EnipQb4AG1laY2uHYOdOB8NHg,159
|
860
862
|
vellum/types/document_read.py,sha256=9LR65w4jvzOg-ji8ioucO2MWUuH4RGvIWrKKu03CNQ4,151
|
861
863
|
vellum/types/document_status.py,sha256=RmqdB8mCPuha4ARvKiG6T60PjyoTFUFxCgzuK9HA1HY,153
|
862
864
|
vellum/types/document_vellum_value.py,sha256=S5stAYdvKrIeKu7HY-DT0s4KYvObKL46ohgRYRVy3VA,159
|
@@ -1385,10 +1387,10 @@ vellum/workflows/nodes/displayable/__init__.py,sha256=6F_4DlSwvHuilWnIalp8iDjjDX
|
|
1385
1387
|
vellum/workflows/nodes/displayable/api_node/__init__.py,sha256=MoxdQSnidIj1Nf_d-hTxlOxcZXaZnsWFDbE-PkTK24o,56
|
1386
1388
|
vellum/workflows/nodes/displayable/api_node/node.py,sha256=QdpsyGVxo5PcN8nwGZpcpW_YMKHr3_VvmbK1BlrdOFk,2547
|
1387
1389
|
vellum/workflows/nodes/displayable/api_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1388
|
-
vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=
|
1390
|
+
vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=Ta-ZkZvllPHpLamiDRdEtVlwBJUFcvBHpyKLY6q06_A,2309
|
1389
1391
|
vellum/workflows/nodes/displayable/bases/__init__.py,sha256=0mWIx3qUrzllV7jqt7wN03vWGMuI1WrrLZeMLT2Cl2c,304
|
1390
1392
|
vellum/workflows/nodes/displayable/bases/api_node/__init__.py,sha256=1jwx4WC358CLA1jgzl_UD-rZmdMm2v9Mps39ndwCD7U,64
|
1391
|
-
vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256
|
1393
|
+
vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256=-LOKjU_rY1UWgD0DS5LJwAClBI8N7zrdmwigE3y5rhc,4000
|
1392
1394
|
vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org3xTvgp1pA0uUXFfnJr29D3HzCey2lEdYF4zbIUgo,70
|
1393
1395
|
vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=nvhoWb8EyRlgtyotYp-wh194n30yQP81UnOH_a8FghY,3140
|
1394
1396
|
vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py,sha256=Hl35IAoepRpE-j4cALaXVJIYTYOF3qszyVbxTj4kS1s,82
|
@@ -1500,8 +1502,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
|
|
1500
1502
|
vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1501
1503
|
vellum/workflows/workflows/tests/test_base_workflow.py,sha256=NRteiICyJvDM5zrtUfq2fZoXcGQVaWC9xmNlLLVW0cU,7979
|
1502
1504
|
vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
|
1503
|
-
vellum_ai-0.14.
|
1504
|
-
vellum_ai-0.14.
|
1505
|
-
vellum_ai-0.14.
|
1506
|
-
vellum_ai-0.14.
|
1507
|
-
vellum_ai-0.14.
|
1505
|
+
vellum_ai-0.14.8.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
|
1506
|
+
vellum_ai-0.14.8.dist-info/METADATA,sha256=rjXn1UMzW7AdriljPfHof7rjjp7GseRXLTfRswDoSBc,5407
|
1507
|
+
vellum_ai-0.14.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1508
|
+
vellum_ai-0.14.8.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
|
1509
|
+
vellum_ai-0.14.8.dist-info/RECORD,,
|
vellum_cli/__init__.py
CHANGED
@@ -361,10 +361,17 @@ def image_push(image: str, tag: Optional[List[str]] = None) -> None:
|
|
361
361
|
|
362
362
|
@workflows.command(name="init")
|
363
363
|
@click.argument("template_name", required=False)
|
364
|
-
|
364
|
+
@click.option(
|
365
|
+
"--target-dir",
|
366
|
+
"target_directory", # Internal parameter name is target_directory
|
367
|
+
type=str,
|
368
|
+
help="""Directory to pull the workflow into. If not specified, \
|
369
|
+
the workflow will be pulled into the current working directory.""",
|
370
|
+
)
|
371
|
+
def workflows_init(template_name: Optional[str] = None, target_directory: Optional[str] = None) -> None:
|
365
372
|
"""Initialize a new Vellum Workflow using a predefined template"""
|
366
373
|
|
367
|
-
init_command(template_name=template_name)
|
374
|
+
init_command(template_name=template_name, target_directory=target_directory)
|
368
375
|
|
369
376
|
|
370
377
|
if __name__ == "__main__":
|
vellum_cli/config.py
CHANGED
@@ -50,6 +50,7 @@ class WorkflowConfig(UniversalBaseModel):
|
|
50
50
|
container_image_name: Optional[str] = None
|
51
51
|
container_image_tag: Optional[str] = None
|
52
52
|
workspace: str = DEFAULT_WORKSPACE_CONFIG.name
|
53
|
+
target_directory: Optional[str] = None
|
53
54
|
|
54
55
|
def merge(self, other: "WorkflowConfig") -> "WorkflowConfig":
|
55
56
|
self_deployment_by_id = {
|
vellum_cli/init.py
CHANGED
@@ -18,7 +18,7 @@ ERROR_LOG_FILE_NAME = "error.log"
|
|
18
18
|
METADATA_FILE_NAME = "metadata.json"
|
19
19
|
|
20
20
|
|
21
|
-
def init_command(template_name: Optional[str] = None):
|
21
|
+
def init_command(template_name: Optional[str] = None, target_directory: Optional[str] = None):
|
22
22
|
load_dotenv()
|
23
23
|
logger = load_cli_logger()
|
24
24
|
config = load_vellum_cli_config()
|
@@ -64,7 +64,11 @@ def init_command(template_name: Optional[str] = None):
|
|
64
64
|
if not pk:
|
65
65
|
raise ValueError("No workflow sandbox ID found in project to pull from.")
|
66
66
|
|
67
|
-
|
67
|
+
# Use target_directory if provided, otherwise use current working directory
|
68
|
+
base_dir = os.path.join(os.getcwd(), target_directory) if target_directory else os.getcwd()
|
69
|
+
target_dir = os.path.join(base_dir, *workflow_config.module.split("."))
|
70
|
+
workflow_config.target_directory = target_dir if target_directory else None
|
71
|
+
|
68
72
|
if os.path.exists(target_dir):
|
69
73
|
click.echo(click.style(f"{target_dir} already exists.", fg="red"))
|
70
74
|
return
|
vellum_cli/pull.py
CHANGED
@@ -179,6 +179,7 @@ def pull_command(
|
|
179
179
|
# Use target_directory if provided, otherwise use current working directory
|
180
180
|
base_dir = os.path.join(os.getcwd(), target_directory) if target_directory else os.getcwd()
|
181
181
|
target_dir = os.path.join(base_dir, *workflow_config.module.split("."))
|
182
|
+
workflow_config.target_directory = target_dir if target_directory else None
|
182
183
|
|
183
184
|
# Delete files in target_dir that aren't in the zip file
|
184
185
|
if os.path.exists(target_dir):
|
vellum_cli/tests/test_init.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import pytest
|
2
1
|
import io
|
3
2
|
import json
|
4
3
|
import os
|
@@ -33,14 +32,7 @@ class MockTemplate:
|
|
33
32
|
self.label = label
|
34
33
|
|
35
34
|
|
36
|
-
|
37
|
-
"base_command",
|
38
|
-
[
|
39
|
-
["workflows", "init"],
|
40
|
-
],
|
41
|
-
ids=["workflows_init"],
|
42
|
-
)
|
43
|
-
def test_init_command(vellum_client, mock_module, base_command):
|
35
|
+
def test_init_command(vellum_client, mock_module):
|
44
36
|
# GIVEN a module on the user's filesystem
|
45
37
|
temp_dir = mock_module.temp_dir
|
46
38
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -52,18 +44,11 @@ def test_init_command(vellum_client, mock_module, base_command):
|
|
52
44
|
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
53
45
|
|
54
46
|
# AND the workflow pull API call returns a zip file
|
55
|
-
vellum_client.workflows.pull.return_value = iter(
|
56
|
-
|
57
|
-
_zip_file_map(
|
58
|
-
{
|
59
|
-
"workflow.py": "print('hello')",
|
60
|
-
}
|
61
|
-
)
|
62
|
-
]
|
63
|
-
)
|
47
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
48
|
+
|
64
49
|
# WHEN the user runs the `init` command and selects the first template
|
65
50
|
runner = CliRunner()
|
66
|
-
result = runner.invoke(cli_main,
|
51
|
+
result = runner.invoke(cli_main, ["workflows", "init"], input="1\n")
|
67
52
|
|
68
53
|
# THEN the command returns successfully
|
69
54
|
assert result.exit_code == 0
|
@@ -94,18 +79,12 @@ def test_init_command(vellum_client, mock_module, base_command):
|
|
94
79
|
"container_image_name": None,
|
95
80
|
"container_image_tag": None,
|
96
81
|
"workspace": "default",
|
82
|
+
"target_directory": None,
|
97
83
|
}
|
98
84
|
]
|
99
85
|
|
100
86
|
|
101
|
-
|
102
|
-
"base_command",
|
103
|
-
[
|
104
|
-
["workflows", "init"],
|
105
|
-
],
|
106
|
-
ids=["workflows_init"],
|
107
|
-
)
|
108
|
-
def test_init_command__invalid_template_id(vellum_client, mock_module, base_command):
|
87
|
+
def test_init_command__invalid_template_id(vellum_client, mock_module):
|
109
88
|
# GIVEN a module on the user's filesystem
|
110
89
|
temp_dir = mock_module.temp_dir
|
111
90
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -121,7 +100,7 @@ def test_init_command__invalid_template_id(vellum_client, mock_module, base_comm
|
|
121
100
|
# Mock click.prompt to raise a KeyboardInterrupt (simulating Ctrl+C)
|
122
101
|
with patch("click.prompt", side_effect=KeyboardInterrupt):
|
123
102
|
runner = CliRunner()
|
124
|
-
result = runner.invoke(cli_main,
|
103
|
+
result = runner.invoke(cli_main, ["workflows", "init"])
|
125
104
|
|
126
105
|
# THEN the command is aborted
|
127
106
|
assert result.exit_code != 0
|
@@ -142,14 +121,7 @@ def test_init_command__invalid_template_id(vellum_client, mock_module, base_comm
|
|
142
121
|
assert lock_data["workflows"] == []
|
143
122
|
|
144
123
|
|
145
|
-
|
146
|
-
"base_command",
|
147
|
-
[
|
148
|
-
["workflows", "init"],
|
149
|
-
],
|
150
|
-
ids=["workflows_init"],
|
151
|
-
)
|
152
|
-
def test_init_command__no_templates(vellum_client, mock_module, base_command):
|
124
|
+
def test_init_command__no_templates(vellum_client, mock_module):
|
153
125
|
# GIVEN a module on the user's filesystem
|
154
126
|
temp_dir = mock_module.temp_dir
|
155
127
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -158,7 +130,7 @@ def test_init_command__no_templates(vellum_client, mock_module, base_command):
|
|
158
130
|
|
159
131
|
# WHEN the user runs the `init` command
|
160
132
|
runner = CliRunner()
|
161
|
-
result = runner.invoke(cli_main,
|
133
|
+
result = runner.invoke(cli_main, ["workflows", "init"])
|
162
134
|
|
163
135
|
# THEN the command gracefully exits
|
164
136
|
assert result.exit_code == 0
|
@@ -179,14 +151,7 @@ def test_init_command__no_templates(vellum_client, mock_module, base_command):
|
|
179
151
|
assert lock_data["workflows"] == []
|
180
152
|
|
181
153
|
|
182
|
-
|
183
|
-
"base_command",
|
184
|
-
[
|
185
|
-
["workflows", "init"],
|
186
|
-
],
|
187
|
-
ids=["workflows_init"],
|
188
|
-
)
|
189
|
-
def test_init_command_target_directory_exists(vellum_client, mock_module, base_command):
|
154
|
+
def test_init_command_target_directory_exists(vellum_client, mock_module):
|
190
155
|
"""
|
191
156
|
GIVEN a target directory already exists
|
192
157
|
WHEN the user tries to run the `init` command
|
@@ -208,19 +173,11 @@ def test_init_command_target_directory_exists(vellum_client, mock_module, base_c
|
|
208
173
|
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
209
174
|
|
210
175
|
# AND the workflow pull API call returns a zip file
|
211
|
-
vellum_client.workflows.pull.return_value = iter(
|
212
|
-
[
|
213
|
-
_zip_file_map(
|
214
|
-
{
|
215
|
-
"workflow.py": "print('hello')",
|
216
|
-
}
|
217
|
-
)
|
218
|
-
]
|
219
|
-
)
|
176
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
220
177
|
|
221
178
|
# WHEN the user runs the `init` command and selects the template
|
222
179
|
runner = CliRunner()
|
223
|
-
result = runner.invoke(cli_main,
|
180
|
+
result = runner.invoke(cli_main, ["workflows", "init"], input="1\n")
|
224
181
|
|
225
182
|
# THEN the command should detect the existing directory and abort
|
226
183
|
assert result.exit_code == 0
|
@@ -244,14 +201,7 @@ def test_init_command_target_directory_exists(vellum_client, mock_module, base_c
|
|
244
201
|
assert lock_data["workflows"] == []
|
245
202
|
|
246
203
|
|
247
|
-
|
248
|
-
"base_command",
|
249
|
-
[
|
250
|
-
["workflows", "init"],
|
251
|
-
],
|
252
|
-
ids=["workflows_init"],
|
253
|
-
)
|
254
|
-
def test_init_command_with_template_name(vellum_client, mock_module, base_command):
|
204
|
+
def test_init_command_with_template_name(vellum_client, mock_module):
|
255
205
|
# GIVEN a module on the user's filesystem
|
256
206
|
temp_dir = mock_module.temp_dir
|
257
207
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -264,14 +214,12 @@ def test_init_command_with_template_name(vellum_client, mock_module, base_comman
|
|
264
214
|
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
265
215
|
|
266
216
|
# AND the workflow pull API call returns a zip file
|
267
|
-
vellum_client.workflows.pull.return_value = iter(
|
268
|
-
[_zip_file_map({"workflow.py": "print('hello')", "README.md": "# Another Workflow\nThis is a test template."})]
|
269
|
-
)
|
217
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
270
218
|
|
271
219
|
# WHEN the user runs the `init` command with a specific template name
|
272
220
|
template_name = snake_case("Another Workflow")
|
273
221
|
runner = CliRunner()
|
274
|
-
result = runner.invoke(cli_main,
|
222
|
+
result = runner.invoke(cli_main, ["workflows", "init", template_name])
|
275
223
|
|
276
224
|
# THEN the command returns successfully
|
277
225
|
assert result.exit_code == 0
|
@@ -305,18 +253,12 @@ def test_init_command_with_template_name(vellum_client, mock_module, base_comman
|
|
305
253
|
"container_image_name": None,
|
306
254
|
"container_image_tag": None,
|
307
255
|
"workspace": "default",
|
256
|
+
"target_directory": None,
|
308
257
|
}
|
309
258
|
]
|
310
259
|
|
311
260
|
|
312
|
-
|
313
|
-
"base_command",
|
314
|
-
[
|
315
|
-
["workflows", "init"],
|
316
|
-
],
|
317
|
-
ids=["workflows_init"],
|
318
|
-
)
|
319
|
-
def test_init_command_with_nonexistent_template_name(vellum_client, mock_module, base_command):
|
261
|
+
def test_init_command_with_nonexistent_template_name(vellum_client, mock_module):
|
320
262
|
# GIVEN a module on the user's filesystem
|
321
263
|
temp_dir = mock_module.temp_dir
|
322
264
|
mock_module.set_pyproject_toml({"workflows": []})
|
@@ -331,7 +273,7 @@ def test_init_command_with_nonexistent_template_name(vellum_client, mock_module,
|
|
331
273
|
# WHEN the user runs the `init` command with a non-existent template name
|
332
274
|
nonexistent_template = "nonexistent_template"
|
333
275
|
runner = CliRunner()
|
334
|
-
result = runner.invoke(cli_main,
|
276
|
+
result = runner.invoke(cli_main, ["workflows", "init", nonexistent_template])
|
335
277
|
|
336
278
|
# THEN the command should indicate the template was not found
|
337
279
|
assert result.exit_code == 0
|
@@ -353,3 +295,179 @@ def test_init_command_with_nonexistent_template_name(vellum_client, mock_module,
|
|
353
295
|
with open(vellum_lock_json) as f:
|
354
296
|
lock_data = json.load(f)
|
355
297
|
assert lock_data["workflows"] == []
|
298
|
+
|
299
|
+
|
300
|
+
def test_init__with_target_dir(vellum_client, mock_module):
|
301
|
+
# GIVEN a module on the user's filesystem
|
302
|
+
temp_dir = mock_module.temp_dir
|
303
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
304
|
+
|
305
|
+
# GIVEN the vellum client returns a list of template workflows
|
306
|
+
fake_templates = [
|
307
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
308
|
+
]
|
309
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
310
|
+
|
311
|
+
# AND the workflow pull API call returns a zip file
|
312
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
313
|
+
|
314
|
+
# AND a target directory
|
315
|
+
target_dir = os.path.join(temp_dir, "dir")
|
316
|
+
os.makedirs(target_dir, exist_ok=True)
|
317
|
+
|
318
|
+
# WHEN the user runs the init command with target-dir
|
319
|
+
runner = CliRunner()
|
320
|
+
result = runner.invoke(cli_main, ["workflows", "init", "--target-dir", target_dir], input="1\n")
|
321
|
+
|
322
|
+
# THEN the command returns successfully
|
323
|
+
assert result.exit_code == 0
|
324
|
+
|
325
|
+
# AND the `workflow.py` file should be created in the target directory
|
326
|
+
module_path = os.path.join(target_dir, "example_workflow")
|
327
|
+
workflow_py = os.path.join(module_path, "workflow.py")
|
328
|
+
assert os.path.exists(workflow_py)
|
329
|
+
with open(workflow_py) as f:
|
330
|
+
assert f.read() == "print('hello')"
|
331
|
+
|
332
|
+
# AND the files are not in the default module directory
|
333
|
+
default_module_path = os.path.join(temp_dir, "example_workflow", "workflow.py")
|
334
|
+
assert not os.path.exists(default_module_path)
|
335
|
+
|
336
|
+
# AND the vellum.lock.json file should be created in the original directory
|
337
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
338
|
+
assert os.path.exists(vellum_lock_json)
|
339
|
+
with open(vellum_lock_json) as f:
|
340
|
+
lock_data = json.load(f)
|
341
|
+
assert lock_data["workflows"] == [
|
342
|
+
{
|
343
|
+
"module": "example_workflow",
|
344
|
+
"workflow_sandbox_id": "template-1",
|
345
|
+
"ignore": None,
|
346
|
+
"deployments": [],
|
347
|
+
"container_image_name": None,
|
348
|
+
"container_image_tag": None,
|
349
|
+
"workspace": "default",
|
350
|
+
"target_directory": module_path,
|
351
|
+
}
|
352
|
+
]
|
353
|
+
|
354
|
+
|
355
|
+
def test_init__with_nested_target_dir(vellum_client, mock_module):
|
356
|
+
# GIVEN a module on the user's filesystem
|
357
|
+
temp_dir = mock_module.temp_dir
|
358
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
359
|
+
|
360
|
+
# GIVEN the vellum client returns a list of template workflows
|
361
|
+
fake_templates = [
|
362
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
363
|
+
]
|
364
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
365
|
+
|
366
|
+
# AND the workflow pull API call returns a zip file
|
367
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
368
|
+
|
369
|
+
# AND a nested target directory that doesn't exist yet
|
370
|
+
nested_target_dir = os.path.join(temp_dir, "dir-1", "dir-2")
|
371
|
+
|
372
|
+
# WHEN the user runs the init command with nested target-dir
|
373
|
+
runner = CliRunner()
|
374
|
+
result = runner.invoke(cli_main, ["workflows", "init", "--target-dir", nested_target_dir], input="1\n")
|
375
|
+
|
376
|
+
# THEN the command returns successfully
|
377
|
+
assert result.exit_code == 0
|
378
|
+
|
379
|
+
# AND the nested directory with module subdirectory should be created
|
380
|
+
module_path = os.path.join(nested_target_dir, "example_workflow")
|
381
|
+
assert os.path.exists(module_path)
|
382
|
+
|
383
|
+
# AND the workflow.py file is written to the nested target directory
|
384
|
+
workflow_py = os.path.join(module_path, "workflow.py")
|
385
|
+
assert os.path.exists(workflow_py)
|
386
|
+
with open(workflow_py) as f:
|
387
|
+
assert f.read() == "print('hello')"
|
388
|
+
|
389
|
+
# AND the files are not in the default module directory
|
390
|
+
default_module_path = os.path.join(temp_dir, "example_workflow", "workflow.py")
|
391
|
+
assert not os.path.exists(default_module_path)
|
392
|
+
|
393
|
+
# AND the vellum.lock.json file is still updated
|
394
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
395
|
+
assert os.path.exists(vellum_lock_json)
|
396
|
+
with open(vellum_lock_json) as f:
|
397
|
+
lock_data = json.load(f)
|
398
|
+
assert lock_data["workflows"] == [
|
399
|
+
{
|
400
|
+
"module": "example_workflow",
|
401
|
+
"workflow_sandbox_id": "template-1",
|
402
|
+
"ignore": None,
|
403
|
+
"deployments": [],
|
404
|
+
"container_image_name": None,
|
405
|
+
"container_image_tag": None,
|
406
|
+
"workspace": "default",
|
407
|
+
"target_directory": module_path,
|
408
|
+
}
|
409
|
+
]
|
410
|
+
|
411
|
+
|
412
|
+
def test_init__with_template_name_and_target_dir(vellum_client, mock_module):
|
413
|
+
# GIVEN a module on the user's filesystem
|
414
|
+
temp_dir = mock_module.temp_dir
|
415
|
+
mock_module.set_pyproject_toml({"workflows": []})
|
416
|
+
|
417
|
+
# GIVEN the vellum client returns a list of template workflows
|
418
|
+
fake_templates = [
|
419
|
+
MockTemplate(id="template-1", label="Example Workflow"),
|
420
|
+
MockTemplate(id="template-2", label="Another Workflow"),
|
421
|
+
]
|
422
|
+
vellum_client.workflow_sandboxes.list_workflow_sandbox_examples.return_value.results = fake_templates
|
423
|
+
|
424
|
+
# AND the workflow pull API call returns a zip file
|
425
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
426
|
+
|
427
|
+
# AND a target directory
|
428
|
+
target_dir = os.path.join(temp_dir, "dir")
|
429
|
+
os.makedirs(target_dir, exist_ok=True)
|
430
|
+
|
431
|
+
# WHEN the user runs the init command with a specific template name and target-dir
|
432
|
+
template_name = snake_case("Another Workflow")
|
433
|
+
runner = CliRunner()
|
434
|
+
result = runner.invoke(cli_main, ["workflows", "init", template_name, "--target-dir", target_dir])
|
435
|
+
|
436
|
+
# THEN the command returns successfully
|
437
|
+
assert result.exit_code == 0
|
438
|
+
|
439
|
+
# AND `vellum_client.workflows.pull` is called with the correct template ID
|
440
|
+
vellum_client.workflows.pull.assert_called_once_with(
|
441
|
+
"template-2", # ID of "Another Workflow"
|
442
|
+
request_options={"additional_query_parameters": {"include_sandbox": True}},
|
443
|
+
)
|
444
|
+
|
445
|
+
# AND the workflow files should be created in the target directory with the correct module subdirectory
|
446
|
+
module_path = os.path.join(target_dir, "another_workflow")
|
447
|
+
workflow_py = os.path.join(module_path, "workflow.py")
|
448
|
+
assert os.path.exists(workflow_py)
|
449
|
+
with open(workflow_py) as f:
|
450
|
+
assert f.read() == "print('hello')"
|
451
|
+
|
452
|
+
# AND the files are not in the default module directory
|
453
|
+
default_module_path = os.path.join(temp_dir, "another_workflow", "workflow.py")
|
454
|
+
assert not os.path.exists(default_module_path)
|
455
|
+
|
456
|
+
# AND the vellum.lock.json file should be created with the correct data
|
457
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
458
|
+
assert os.path.exists(vellum_lock_json)
|
459
|
+
|
460
|
+
with open(vellum_lock_json) as f:
|
461
|
+
lock_data = json.load(f)
|
462
|
+
assert lock_data["workflows"] == [
|
463
|
+
{
|
464
|
+
"module": "another_workflow",
|
465
|
+
"workflow_sandbox_id": "template-2",
|
466
|
+
"ignore": None,
|
467
|
+
"deployments": [],
|
468
|
+
"container_image_name": None,
|
469
|
+
"container_image_tag": None,
|
470
|
+
"workspace": "default",
|
471
|
+
"target_directory": module_path,
|
472
|
+
}
|
473
|
+
]
|
vellum_cli/tests/test_pull.py
CHANGED
@@ -73,6 +73,7 @@ def test_pull(vellum_client, mock_module, base_command):
|
|
73
73
|
"ignore": None,
|
74
74
|
"deployments": [],
|
75
75
|
"workspace": "default",
|
76
|
+
"target_directory": None,
|
76
77
|
}
|
77
78
|
],
|
78
79
|
"workspaces": [],
|
@@ -167,6 +168,7 @@ def test_pull__with_target_dir(vellum_client, mock_module, base_command):
|
|
167
168
|
"ignore": None,
|
168
169
|
"deployments": [],
|
169
170
|
"workspace": "default",
|
171
|
+
"target_directory": module_path,
|
170
172
|
}
|
171
173
|
],
|
172
174
|
"workspaces": [],
|
@@ -233,6 +235,7 @@ def test_pull__with_nested_target_dir(vellum_client, mock_module, base_command):
|
|
233
235
|
"ignore": None,
|
234
236
|
"deployments": [],
|
235
237
|
"workspace": "default",
|
238
|
+
"target_directory": module_path,
|
236
239
|
}
|
237
240
|
],
|
238
241
|
"workspaces": [],
|
@@ -289,6 +292,7 @@ def test_pull__sandbox_id_with_no_config(vellum_client):
|
|
289
292
|
"container_image_tag": None,
|
290
293
|
"container_image_name": None,
|
291
294
|
"workspace": "default",
|
295
|
+
"target_directory": None,
|
292
296
|
}
|
293
297
|
],
|
294
298
|
}
|
@@ -372,6 +376,7 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
|
|
372
376
|
"container_image_tag": None,
|
373
377
|
"container_image_name": None,
|
374
378
|
"workspace": "default",
|
379
|
+
"target_directory": None,
|
375
380
|
}
|
376
381
|
],
|
377
382
|
"workspaces": [],
|
@@ -619,6 +624,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
619
624
|
"container_image_name": None,
|
620
625
|
"container_image_tag": None,
|
621
626
|
"workspace": "default",
|
627
|
+
"target_directory": None,
|
622
628
|
},
|
623
629
|
{
|
624
630
|
"module": "super_cool_workflow",
|
@@ -628,6 +634,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
628
634
|
"container_image_name": "test",
|
629
635
|
"container_image_tag": "1.0",
|
630
636
|
"workspace": "default",
|
637
|
+
"target_directory": None,
|
631
638
|
},
|
632
639
|
]
|
633
640
|
|
@@ -771,6 +778,7 @@ def test_pull__module_not_in_config(vellum_client, mock_module):
|
|
771
778
|
"container_image_name": None,
|
772
779
|
"container_image_tag": None,
|
773
780
|
"workspace": "default",
|
781
|
+
"target_directory": None,
|
774
782
|
}
|
775
783
|
]
|
776
784
|
|
vellum_cli/tests/test_push.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from uuid import UUID
|
2
|
+
from typing import Dict
|
2
3
|
|
3
4
|
from vellum.workflows.inputs import BaseInputs
|
4
5
|
from vellum.workflows.nodes import BaseNode
|
@@ -145,3 +146,85 @@ def test_vellum_workflow_display_serialize_valid_handle_ids_for_base_nodes():
|
|
145
146
|
assert (
|
146
147
|
node["trigger"]["id"] in edge_target_handle_ids
|
147
148
|
), f"Trigger {node['trigger']['id']} from node {node['label']} not found in edge target handle ids"
|
149
|
+
|
150
|
+
|
151
|
+
def test_vellum_workflow_display__serialize_with_unused_nodes_and_edges():
|
152
|
+
# GIVEN a workflow with active and unused nodes
|
153
|
+
class NodeA(BaseNode):
|
154
|
+
class Outputs(BaseNode.Outputs):
|
155
|
+
result: str
|
156
|
+
|
157
|
+
class NodeB(BaseNode):
|
158
|
+
pass
|
159
|
+
|
160
|
+
class NodeC(BaseNode):
|
161
|
+
pass
|
162
|
+
|
163
|
+
# AND A workflow that uses them correctly
|
164
|
+
class Workflow(BaseWorkflow):
|
165
|
+
graph = NodeA
|
166
|
+
unused_graphs = {NodeB >> NodeC}
|
167
|
+
|
168
|
+
class Outputs(BaseWorkflow.Outputs):
|
169
|
+
final = NodeA.Outputs.result
|
170
|
+
|
171
|
+
# WHEN we serialize it
|
172
|
+
workflow_display = get_workflow_display(
|
173
|
+
base_display_class=VellumWorkflowDisplay,
|
174
|
+
workflow_class=Workflow,
|
175
|
+
)
|
176
|
+
|
177
|
+
# WHEN we serialize the workflow
|
178
|
+
exec_config = workflow_display.serialize()
|
179
|
+
|
180
|
+
# THEN the serialized workflow contains the expected nodes and edges
|
181
|
+
raw_data = exec_config["workflow_raw_data"]
|
182
|
+
assert isinstance(raw_data, dict)
|
183
|
+
|
184
|
+
nodes = raw_data["nodes"]
|
185
|
+
edges = raw_data["edges"]
|
186
|
+
|
187
|
+
assert isinstance(nodes, list)
|
188
|
+
assert isinstance(edges, list)
|
189
|
+
|
190
|
+
# Find nodes by their definition name
|
191
|
+
node_ids: Dict[str, str] = {}
|
192
|
+
|
193
|
+
for node in nodes:
|
194
|
+
assert isinstance(node, dict)
|
195
|
+
definition = node.get("definition")
|
196
|
+
if definition is None:
|
197
|
+
continue
|
198
|
+
|
199
|
+
assert isinstance(definition, dict)
|
200
|
+
name = definition.get("name")
|
201
|
+
if not isinstance(name, str):
|
202
|
+
continue
|
203
|
+
|
204
|
+
if name in ["NodeA", "NodeB", "NodeC"]:
|
205
|
+
node_id = node.get("id")
|
206
|
+
if isinstance(node_id, str):
|
207
|
+
node_ids[name] = node_id
|
208
|
+
|
209
|
+
# Verify all nodes are present
|
210
|
+
assert "NodeA" in node_ids, "Active node NodeA not found in serialized output"
|
211
|
+
assert "NodeB" in node_ids, "Unused node NodeB not found in serialized output"
|
212
|
+
assert "NodeC" in node_ids, "Unused node NodeC not found in serialized output"
|
213
|
+
|
214
|
+
# Verify the edge between NodeB and NodeC is present
|
215
|
+
edge_found = False
|
216
|
+
for edge in edges:
|
217
|
+
assert isinstance(edge, dict)
|
218
|
+
source_id = edge.get("source_node_id")
|
219
|
+
target_id = edge.get("target_node_id")
|
220
|
+
|
221
|
+
if (
|
222
|
+
isinstance(source_id, str)
|
223
|
+
and isinstance(target_id, str)
|
224
|
+
and source_id == node_ids["NodeB"]
|
225
|
+
and target_id == node_ids["NodeC"]
|
226
|
+
):
|
227
|
+
edge_found = True
|
228
|
+
break
|
229
|
+
|
230
|
+
assert edge_found, "Edge between unused nodes NodeB and NodeC not found in serialized output"
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import pytest
|
2
1
|
from uuid import uuid4
|
3
2
|
|
4
3
|
from deepdiff import DeepDiff
|
@@ -217,7 +216,6 @@ def test_serialize_node__try(serialize_node):
|
|
217
216
|
)
|
218
217
|
|
219
218
|
|
220
|
-
@pytest.mark.skip(reason="Not implemented")
|
221
219
|
def test_serialize_node__stacked():
|
222
220
|
@TryNode.wrap()
|
223
221
|
@RetryNode.wrap(max_attempts=5)
|
@@ -236,4 +234,121 @@ def test_serialize_node__stacked():
|
|
236
234
|
exec_config = workflow_display.serialize()
|
237
235
|
|
238
236
|
# THEN the workflow display is created successfully
|
239
|
-
assert
|
237
|
+
assert not DeepDiff(
|
238
|
+
{
|
239
|
+
"workflow_raw_data": {
|
240
|
+
"nodes": [
|
241
|
+
{
|
242
|
+
"id": "c14c1c9b-a7a4-4d2c-84fb-c940cfb09525",
|
243
|
+
"type": "ENTRYPOINT",
|
244
|
+
"inputs": [],
|
245
|
+
"data": {
|
246
|
+
"label": "Entrypoint Node",
|
247
|
+
"source_handle_id": "51a5eb25-af14-4bee-9ced-d2aa534ea8e9",
|
248
|
+
},
|
249
|
+
"display_data": {"position": {"x": 0.0, "y": 0.0}},
|
250
|
+
"base": None,
|
251
|
+
"definition": None,
|
252
|
+
},
|
253
|
+
{
|
254
|
+
"id": "074833b0-e142-4bbc-8dec-209a35e178a3",
|
255
|
+
"label": "test_serialize_node__stacked.<locals>.InnerStackedGenericNode",
|
256
|
+
"type": "GENERIC",
|
257
|
+
"display_data": {"position": {"x": 0.0, "y": 0.0}},
|
258
|
+
"base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
|
259
|
+
"definition": {
|
260
|
+
"name": "InnerStackedGenericNode",
|
261
|
+
"module": [
|
262
|
+
"vellum_ee",
|
263
|
+
"workflows",
|
264
|
+
"display",
|
265
|
+
"tests",
|
266
|
+
"workflow_serialization",
|
267
|
+
"generic_nodes",
|
268
|
+
"test_adornments_serialization",
|
269
|
+
],
|
270
|
+
},
|
271
|
+
"trigger": {"id": "f206358d-04a5-41c9-beee-0871a074fa48", "merge_behavior": "AWAIT_ATTRIBUTES"},
|
272
|
+
"ports": [{"id": "408cd5fb-3a3e-4eb2-9889-61111bd6a129", "name": "default", "type": "DEFAULT"}],
|
273
|
+
"adornments": [
|
274
|
+
{
|
275
|
+
"id": "5be7d260-74f7-4734-b31b-a46a94539586",
|
276
|
+
"label": "RetryNode",
|
277
|
+
"base": {
|
278
|
+
"name": "RetryNode",
|
279
|
+
"module": ["vellum", "workflows", "nodes", "core", "retry_node", "node"],
|
280
|
+
},
|
281
|
+
"attributes": [
|
282
|
+
{
|
283
|
+
"id": "c91782e3-140f-4938-9c23-d2a7b85dcdd8",
|
284
|
+
"name": "retry_on_error_code",
|
285
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
286
|
+
},
|
287
|
+
{
|
288
|
+
"id": "f388e93b-8c68-4f54-8577-bbd0c9091557",
|
289
|
+
"name": "max_attempts",
|
290
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "NUMBER", "value": 5}},
|
291
|
+
},
|
292
|
+
{
|
293
|
+
"id": "8a07dc58-3fed-41d4-8ca6-31ee0bb86c61",
|
294
|
+
"name": "delay",
|
295
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
296
|
+
},
|
297
|
+
{
|
298
|
+
"id": "73a02e62-4535-4e1f-97b5-1264ca8b1d71",
|
299
|
+
"name": "retry_on_condition",
|
300
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
301
|
+
},
|
302
|
+
],
|
303
|
+
},
|
304
|
+
{
|
305
|
+
"id": "3344083c-a32c-4a32-920b-0fb5093448fa",
|
306
|
+
"label": "TryNode",
|
307
|
+
"base": {
|
308
|
+
"name": "TryNode",
|
309
|
+
"module": ["vellum", "workflows", "nodes", "core", "try_node", "node"],
|
310
|
+
},
|
311
|
+
"attributes": [
|
312
|
+
{
|
313
|
+
"id": "ab2fbab0-e2a0-419b-b1ef-ce11ecf11e90",
|
314
|
+
"name": "on_error_code",
|
315
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
316
|
+
}
|
317
|
+
],
|
318
|
+
},
|
319
|
+
],
|
320
|
+
"attributes": [],
|
321
|
+
"outputs": [],
|
322
|
+
},
|
323
|
+
],
|
324
|
+
"edges": [
|
325
|
+
{
|
326
|
+
"id": "e8bd50dd-37a0-49b0-8b7b-f1dd8eb478b9",
|
327
|
+
"source_node_id": "c14c1c9b-a7a4-4d2c-84fb-c940cfb09525",
|
328
|
+
"source_handle_id": "51a5eb25-af14-4bee-9ced-d2aa534ea8e9",
|
329
|
+
"target_node_id": "074833b0-e142-4bbc-8dec-209a35e178a3",
|
330
|
+
"target_handle_id": "f206358d-04a5-41c9-beee-0871a074fa48",
|
331
|
+
"type": "DEFAULT",
|
332
|
+
}
|
333
|
+
],
|
334
|
+
"display_data": {"viewport": {"x": 0.0, "y": 0.0, "zoom": 1.0}},
|
335
|
+
"definition": {
|
336
|
+
"name": "StackedWorkflow",
|
337
|
+
"module": [
|
338
|
+
"vellum_ee",
|
339
|
+
"workflows",
|
340
|
+
"display",
|
341
|
+
"tests",
|
342
|
+
"workflow_serialization",
|
343
|
+
"generic_nodes",
|
344
|
+
"test_adornments_serialization",
|
345
|
+
],
|
346
|
+
},
|
347
|
+
},
|
348
|
+
"input_variables": [],
|
349
|
+
"state_variables": [],
|
350
|
+
"output_variables": [],
|
351
|
+
},
|
352
|
+
exec_config,
|
353
|
+
ignore_order=True,
|
354
|
+
)
|
@@ -217,23 +217,30 @@ class BaseWorkflowDisplay(
|
|
217
217
|
# TODO: We should still serialize nodes that are in the workflow's directory but aren't used in the graph.
|
218
218
|
# https://app.shortcut.com/vellum/story/5394
|
219
219
|
for node in self._workflow.get_nodes():
|
220
|
-
|
220
|
+
extracted_node_displays = self._extract_node_displays(node)
|
221
221
|
|
222
|
-
|
223
|
-
|
222
|
+
for extracted_node, extracted_node_display in extracted_node_displays.items():
|
223
|
+
if extracted_node not in node_displays:
|
224
|
+
node_displays[extracted_node] = extracted_node_display
|
224
225
|
|
225
|
-
|
226
|
-
|
226
|
+
if extracted_node not in global_node_displays:
|
227
|
+
global_node_displays[extracted_node] = extracted_node_display
|
227
228
|
|
228
|
-
|
229
|
-
|
230
|
-
if inner_node:
|
231
|
-
inner_node_display = self._get_node_display(inner_node)
|
232
|
-
node_displays[inner_node] = inner_node_display
|
233
|
-
global_node_displays[inner_node] = inner_node_display
|
229
|
+
self._enrich_global_node_output_displays(node, extracted_node_displays[node], global_node_output_displays)
|
230
|
+
self._enrich_node_port_displays(node, extracted_node_displays[node], port_displays)
|
234
231
|
|
235
|
-
|
236
|
-
self.
|
232
|
+
for node in self._workflow.get_unused_nodes():
|
233
|
+
extracted_node_displays = self._extract_node_displays(node)
|
234
|
+
|
235
|
+
for extracted_node, extracted_node_display in extracted_node_displays.items():
|
236
|
+
if extracted_node not in node_displays:
|
237
|
+
node_displays[extracted_node] = extracted_node_display
|
238
|
+
|
239
|
+
if extracted_node not in global_node_displays:
|
240
|
+
global_node_displays[extracted_node] = extracted_node_display
|
241
|
+
|
242
|
+
self._enrich_global_node_output_displays(node, extracted_node_displays[node], global_node_output_displays)
|
243
|
+
self._enrich_node_port_displays(node, extracted_node_displays[node], port_displays)
|
237
244
|
|
238
245
|
workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = {}
|
239
246
|
# If we're dealing with a nested workflow, then it should have access to the inputs of its parents.
|
@@ -280,6 +287,15 @@ class BaseWorkflowDisplay(
|
|
280
287
|
edge, node_displays, port_displays, overrides=edge_display_overrides
|
281
288
|
)
|
282
289
|
|
290
|
+
for edge in self._workflow.get_unused_edges():
|
291
|
+
if edge in edge_displays:
|
292
|
+
continue
|
293
|
+
|
294
|
+
edge_display_overrides = self.edge_displays.get((edge.from_port, edge.to_node))
|
295
|
+
edge_displays[(edge.from_port, edge.to_node)] = self._generate_edge_display(
|
296
|
+
edge, node_displays, port_displays, overrides=edge_display_overrides
|
297
|
+
)
|
298
|
+
|
283
299
|
workflow_output_displays: Dict[BaseDescriptor, WorkflowOutputDisplay] = {}
|
284
300
|
for workflow_output in self._workflow.Outputs:
|
285
301
|
if workflow_output in workflow_output_displays:
|
@@ -409,3 +425,20 @@ class BaseWorkflowDisplay(
|
|
409
425
|
node_displays=temp_node_displays,
|
410
426
|
)
|
411
427
|
return display_meta
|
428
|
+
|
429
|
+
def _extract_node_displays(self, node: Type[BaseNode]) -> Dict[Type[BaseNode], NodeDisplayType]:
|
430
|
+
node_display = self._get_node_display(node)
|
431
|
+
additional_node_displays: Dict[Type[BaseNode], NodeDisplayType] = {
|
432
|
+
node: node_display,
|
433
|
+
}
|
434
|
+
|
435
|
+
# Nodes wrapped in a decorator need to be in our node display dictionary for later retrieval
|
436
|
+
inner_node = get_wrapped_node(node)
|
437
|
+
if inner_node:
|
438
|
+
inner_node_displays = self._extract_node_displays(inner_node)
|
439
|
+
|
440
|
+
for node, display in inner_node_displays.items():
|
441
|
+
if node not in additional_node_displays:
|
442
|
+
additional_node_displays[node] = display
|
443
|
+
|
444
|
+
return additional_node_displays
|
@@ -126,6 +126,18 @@ class VellumWorkflowDisplay(
|
|
126
126
|
|
127
127
|
nodes.append(serialized_node)
|
128
128
|
|
129
|
+
# Add all unused nodes in the workflow
|
130
|
+
for node in self._workflow.get_unused_nodes():
|
131
|
+
node_display = self.display_context.node_displays[node]
|
132
|
+
|
133
|
+
try:
|
134
|
+
serialized_node = node_display.serialize(self.display_context)
|
135
|
+
except NotImplementedError as e:
|
136
|
+
self.add_error(e)
|
137
|
+
continue
|
138
|
+
|
139
|
+
nodes.append(serialized_node)
|
140
|
+
|
129
141
|
synthetic_output_edges: JsonArray = []
|
130
142
|
output_variables: JsonArray = []
|
131
143
|
final_output_nodes = [
|
File without changes
|
File without changes
|
File without changes
|