vellum-ai 0.14.33__py3-none-any.whl → 0.14.34__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.
@@ -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.33",
21
+ "X-Fern-SDK-Version": "0.14.34",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -1,6 +1,8 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
+ from __future__ import annotations
3
4
  from ..core.pydantic_utilities import UniversalBaseModel
5
+ from .array_vellum_value import ArrayVellumValue
4
6
  import datetime as dt
5
7
  from .release_environment import ReleaseEnvironment
6
8
  import typing
@@ -10,6 +12,7 @@ from .release_release_tag import ReleaseReleaseTag
10
12
  from .slim_release_review import SlimReleaseReview
11
13
  from ..core.pydantic_utilities import IS_PYDANTIC_V2
12
14
  import pydantic
15
+ from ..core.pydantic_utilities import update_forward_refs
13
16
 
14
17
 
15
18
  class WorkflowDeploymentRelease(UniversalBaseModel):
@@ -30,3 +33,6 @@ class WorkflowDeploymentRelease(UniversalBaseModel):
30
33
  frozen = True
31
34
  smart_union = True
32
35
  extra = pydantic.Extra.allow
36
+
37
+
38
+ update_forward_refs(ArrayVellumValue, WorkflowDeploymentRelease=WorkflowDeploymentRelease)
@@ -1,13 +1,19 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
+ from __future__ import annotations
3
4
  from ..core.pydantic_utilities import UniversalBaseModel
4
- from ..core.pydantic_utilities import IS_PYDANTIC_V2
5
+ from .array_vellum_value import ArrayVellumValue
5
6
  import typing
7
+ from .vellum_variable import VellumVariable
8
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2
6
9
  import pydantic
10
+ from ..core.pydantic_utilities import update_forward_refs
7
11
 
8
12
 
9
13
  class WorkflowDeploymentReleaseWorkflowVersion(UniversalBaseModel):
10
14
  id: str
15
+ input_variables: typing.List[VellumVariable]
16
+ output_variables: typing.List[VellumVariable]
11
17
 
12
18
  if IS_PYDANTIC_V2:
13
19
  model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
@@ -17,3 +23,6 @@ class WorkflowDeploymentReleaseWorkflowVersion(UniversalBaseModel):
17
23
  frozen = True
18
24
  smart_union = True
19
25
  extra = pydantic.Extra.allow
26
+
27
+
28
+ update_forward_refs(ArrayVellumValue, WorkflowDeploymentReleaseWorkflowVersion=WorkflowDeploymentReleaseWorkflowVersion)
@@ -75,9 +75,7 @@ class InlineSubworkflowNode(
75
75
  with execution_context(parent_context=get_parent_context()):
76
76
  subworkflow = self.subworkflow(
77
77
  parent_state=self.state,
78
- context=WorkflowContext(
79
- vellum_client=self._context.vellum_client, generated_files=self._context.generated_files
80
- ),
78
+ context=WorkflowContext.create_from(self._context),
81
79
  )
82
80
  subworkflow_stream = subworkflow.stream(
83
81
  inputs=self._compile_subworkflow_inputs(),
@@ -171,9 +171,7 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
171
171
  self._run_subworkflow(item=item, index=index)
172
172
 
173
173
  def _run_subworkflow(self, *, item: MapNodeItemType, index: int) -> None:
174
- context = WorkflowContext(
175
- vellum_client=self._context.vellum_client, generated_files=self._context.generated_files
176
- )
174
+ context = WorkflowContext.create_from(self._context)
177
175
  subworkflow = self.subworkflow(
178
176
  parent_state=self.state,
179
177
  context=context,
@@ -45,9 +45,7 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
45
45
  with execution_context(parent_context=parent_context):
46
46
  subworkflow = self.subworkflow(
47
47
  parent_state=self.state,
48
- context=WorkflowContext(
49
- vellum_client=self._context.vellum_client, generated_files=self._context.generated_files
50
- ),
48
+ context=WorkflowContext.create_from(self._context),
51
49
  )
52
50
  subworkflow_stream = subworkflow.stream(
53
51
  inputs=self.SubworkflowInputs(attempt_number=attempt_number),
@@ -32,9 +32,7 @@ class TryNode(BaseAdornmentNode[StateType], Generic[StateType]):
32
32
  with execution_context(parent_context=parent_context):
33
33
  subworkflow = self.subworkflow(
34
34
  parent_state=self.state,
35
- context=WorkflowContext(
36
- vellum_client=self._context.vellum_client, generated_files=self._context.generated_files
37
- ),
35
+ context=WorkflowContext.create_from(self._context),
38
36
  )
39
37
  subworkflow_stream = subworkflow.stream(
40
38
  event_filter=all_workflow_event_filter,
@@ -1,5 +1,6 @@
1
1
  import pytest
2
2
  import os
3
+ import re
3
4
  from typing import Any, List, Union
4
5
 
5
6
  from pydantic import BaseModel
@@ -653,7 +654,9 @@ def main():
653
654
  node.run()
654
655
 
655
656
  # AND the error should contain the execution error details
656
- assert exc_info.value.message == "Expected an output of type 'int | float', but received 'str'"
657
+ assert re.match(
658
+ r"Expected an output of type '(int \| float|float \| int)', but received 'str'", exc_info.value.message
659
+ )
657
660
 
658
661
 
659
662
  def test_run_node__chat_history_output_type():
@@ -74,3 +74,7 @@ class WorkflowContext:
74
74
 
75
75
  def _get_all_node_output_mocks(self) -> List[MockNodeExecution]:
76
76
  return [mock for mocks in self._node_output_mocks_map.values() for mock in mocks]
77
+
78
+ @classmethod
79
+ def create_from(cls, context):
80
+ return cls(vellum_client=context.vellum_client, generated_files=context.generated_files)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.33
3
+ Version: 0.14.34
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -7,7 +7,7 @@ vellum_cli/image_push.py,sha256=8DDvRDJEZ-FukUCqGW1827bg1ybF4xBbx9WyqWYQE-g,6816
7
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=p_BCCRjgPhng6JktuECtkDQLbhopt6JpmrtGoLnLJT8,1161
10
- vellum_cli/pull.py,sha256=OfP2FIChfYzi7ksWObnvmkibln0JubMQftr1TZL-Bxk,10061
10
+ vellum_cli/pull.py,sha256=YTo5cVCcp7RjS9lHednOuud4rW4bH7jqV3GMdbic_Uk,12002
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
@@ -16,7 +16,7 @@ vellum_cli/tests/test_image_push.py,sha256=i3lJuW8nFRwL1M1OF6752IZYvGAFgKmkB2hd_
16
16
  vellum_cli/tests/test_init.py,sha256=8UOc_ThfouR4ja5cCl_URuLk7ohr9JXfCnG4yka1OUQ,18754
17
17
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
18
18
  vellum_cli/tests/test_ping.py,sha256=3ucVRThEmTadlV9LrJdCCrr1Ofj3rOjG6ue0BNR2UC0,2523
19
- vellum_cli/tests/test_pull.py,sha256=UmgQwtv8J7U2zkbTvOPbxi6ozvAec1Fo1b2E5Fc9gWU,34625
19
+ vellum_cli/tests/test_pull.py,sha256=fci5idbcAsnvfJcmP8KlULX4w4gkjLtXkn3iV4wsLZA,46472
20
20
  vellum_cli/tests/test_push.py,sha256=zDv_Q1hbXtLwmTJDPRAvwDjbuHC09uNRYOy4FQujUow,23476
21
21
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -121,14 +121,14 @@ vellum_ee/workflows/tests/local_workflow/nodes/final_output.py,sha256=ZX7zBv87zi
121
121
  vellum_ee/workflows/tests/local_workflow/nodes/templating_node.py,sha256=NQwFN61QkHfI3Vssz-B0NKGfupK8PU0FDSAIAhYBLi0,325
122
122
  vellum_ee/workflows/tests/local_workflow/workflow.py,sha256=A4qOzOPNwePYxWbcAgIPLsmrVS_aVEZEc-wULSv787Q,393
123
123
  vellum_ee/workflows/tests/test_display_meta.py,sha256=C25dErwghPNXio49pvSRxyOuc96srH6eYEwTAWdE2zY,2258
124
- vellum_ee/workflows/tests/test_server.py,sha256=Q9XD93jMwYjZOkcwMPNM2F4BWvcY71wxkYp8HgMgGFU,4435
124
+ vellum_ee/workflows/tests/test_server.py,sha256=SsOkS6sGO7uGC4mxvk4iv8AtcXs058P9hgFHzTWmpII,14519
125
125
  vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
126
126
  vellum/__init__.py,sha256=Ur02Hk_LtwXW45f-LBBtBhRX1xJRSq05ytaQUfgIGmc,40970
127
127
  vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
128
128
  vellum/client/__init__.py,sha256=ki-TDOmYzC0FePN7swDyE6UpHFQV_4dK7lqy4h-3s1Y,118148
129
129
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
130
130
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
131
- vellum/client/core/client_wrapper.py,sha256=jPo42cS6hlGPneWIZtcMfaVmCbt4TEQwCPgoWxxCTUw,1869
131
+ vellum/client/core/client_wrapper.py,sha256=-wKm98fWVAP4wszQZhj8UDobt3IotWN6IYciYhNcNck,1869
132
132
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
133
133
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
134
134
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -698,8 +698,8 @@ vellum/client/types/workflow_deployment_event_executions_response.py,sha256=x7mZ
698
698
  vellum/client/types/workflow_deployment_history_item.py,sha256=4WUPzcthBvEZ7iaisKfEg0soUtHjcTEnL_VUVaKpTyw,1420
699
699
  vellum/client/types/workflow_deployment_parent_context.py,sha256=QNyPj2o-jauaC48KrRjCWan1IKIbDgyuxLxURmhXsXM,2347
700
700
  vellum/client/types/workflow_deployment_read.py,sha256=tp1WaojTVE_dz1oiZ97h8ixMbIWDgy2yRu08A7wPMpw,2363
701
- vellum/client/types/workflow_deployment_release.py,sha256=pLkRtkCIEUUZuYxdfXELddVKkljqt42OG7fUA8HXt4M,1209
702
- vellum/client/types/workflow_deployment_release_workflow_version.py,sha256=kTZbVa6KAtAD_nCfea3QlQrZTH2AYP9tbaOKimHp9jg,581
701
+ vellum/client/types/workflow_deployment_release.py,sha256=UshzYZE5Fv1hAwztaMTD1Fs0JNQqbeNus0IrwXLkKL0,1444
702
+ vellum/client/types/workflow_deployment_release_workflow_version.py,sha256=NP3FoxLpgMUIK1OAPf_ei58mxE9F7BLAuw7q55CIcT8,989
703
703
  vellum/client/types/workflow_error.py,sha256=EQajkEmLS64T0wYm0goHQl0rT7Lguurk8pLwkhjsgAI,282
704
704
  vellum/client/types/workflow_event_display_context.py,sha256=tnO9lgIJKnLtuS6xum_QilI83LjOmnWCLtnSHLn1oNo,929
705
705
  vellum/client/types/workflow_event_error.py,sha256=HIewu_kh3KNPpWegAQArvAGHCp-cBIXqlUAAc_dBZhc,687
@@ -1523,22 +1523,22 @@ vellum/workflows/nodes/core/__init__.py,sha256=5zDMCmyt1v0HTJzlUBwq3U9L825yZGZhT
1523
1523
  vellum/workflows/nodes/core/error_node/__init__.py,sha256=g7RRnlHhqu4qByfLjBwCunmgGA8dI5gNsjS3h6TwlSI,60
1524
1524
  vellum/workflows/nodes/core/error_node/node.py,sha256=MFHU5vITYSK-L9CuMZ49In2ZeNLWnhZD0f8r5dWvb5Y,1270
1525
1525
  vellum/workflows/nodes/core/inline_subworkflow_node/__init__.py,sha256=nKNEH1QTl-1PcvmYoqSWEl0-t6gAur8GLTXHzklRQfM,84
1526
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=gumdZs50XGpnFvKxde-WAu6NqH7ImwRf3gK6c7B46W0,6696
1526
+ vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=rgcjc3gaCEX9uSfkLSErHjSnNQEeqREVZk-7TEr9hUo,6595
1527
1527
  vellum/workflows/nodes/core/inline_subworkflow_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1528
1528
  vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py,sha256=kUqwcRMMxjTHALbwGUXCJT_aJBrHS1bkg49oL8R0JO8,4337
1529
1529
  vellum/workflows/nodes/core/map_node/__init__.py,sha256=MXpZYmGfhsMJHqqlpd64WiJRtbAtAMQz-_3fCU_cLV0,56
1530
- vellum/workflows/nodes/core/map_node/node.py,sha256=8AOHvV1TR8Tr4IyDjdIk6Jzi86KJpg_wS9gp30tDRLQ,9312
1530
+ vellum/workflows/nodes/core/map_node/node.py,sha256=RER4mLOXSe1BSpjEYCoJq83ZeydQ5Q5uKCZAsqhlOW8,9227
1531
1531
  vellum/workflows/nodes/core/map_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1532
1532
  vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=uMR0AyIFn539LqTKHdwuBswnx1i-PHyqPpgtYrnmYMY,3496
1533
1533
  vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
1534
- vellum/workflows/nodes/core/retry_node/node.py,sha256=UmTxbxom2VPBoJLk75gXdMa300JJ-1rkW0rh2MeF3jE,5354
1534
+ vellum/workflows/nodes/core/retry_node/node.py,sha256=at7RjwUmlBeUv-tHvqeOhCAxkyuSw47ySmIQKC0fJf8,5245
1535
1535
  vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1536
1536
  vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=RM_OHwxrHwyxvlQQBJPqVBxpedFuWQ9h2-Xa3kP75sc,4399
1537
1537
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1538
1538
  vellum/workflows/nodes/core/templating_node/node.py,sha256=iqBmr2i-f-BqhisNQJiDfewjol0ur7-XpupLStyMJsg,3731
1539
1539
  vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=nXkgGDBg4wP36NwykdMEVWwx_xjv8oGT2rYkwuCB_VU,10075
1540
1540
  vellum/workflows/nodes/core/try_node/__init__.py,sha256=JVD4DrldTIqFQQFrubs9KtWCCc0YCAc7Fzol5ZWIWeM,56
1541
- vellum/workflows/nodes/core/try_node/node.py,sha256=-fHfcgI9RFQobNyiugP2dow4ahqY7iDd1LObJw5aB8k,4530
1541
+ vellum/workflows/nodes/core/try_node/node.py,sha256=2r2I70j30IyZPvn3Q3zP2VgLbx3WQ1DdOr8NUlXjcq0,4429
1542
1542
  vellum/workflows/nodes/core/try_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1543
1543
  vellum/workflows/nodes/core/try_node/tests/test_node.py,sha256=h6eUc3SggvhzBWlOD0PrPUlkoCSQHwjqYn81VkxSIxU,4948
1544
1544
  vellum/workflows/nodes/displayable/__init__.py,sha256=6F_4DlSwvHuilWnIalp8iDjjDXl0Nmz4QzJV2PYe5RI,1023
@@ -1567,7 +1567,7 @@ vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=Ko_Dy17Ajf
1567
1567
  vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1568
1568
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1569
1569
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
1570
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=xAaoOfQHQUlp0iKlig87t0aT2cJM-8PxiTb1QDg8VmY,24641
1570
+ vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=AzgTK2YSvVj7zr6gWZfz0-YGf1cVQ9DiSx9fe5BR4uE,24690
1571
1571
  vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=G-sc7yOL5g6rLk99X8HAbXNcLxRaqpju9IXq1iUwnQI,4470
1572
1572
  vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
1573
1573
  vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=Qjfl33gZ3JEgxBA1EgzSUebboGvsARthIxxcQyvx5Gg,1152
@@ -1638,7 +1638,7 @@ vellum/workflows/runner/runner.py,sha256=ww4fjZJBENkB5HJxdj92kTz7k_EyifCeAreupy5
1638
1638
  vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
1639
1639
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
1640
1640
  vellum/workflows/state/base.py,sha256=Vkhneko3VlQrPsMLU1PYSzXU_W1u7_AraJsghiv5O-4,15512
1641
- vellum/workflows/state/context.py,sha256=B-sGK9GpZPh7bgCPb8PJCrl_evod3Ru7rHs_RofisrU,2926
1641
+ vellum/workflows/state/context.py,sha256=KOAI1wEGn8dGmhmAemJaf4SZbitP3jpIBcwKfznQaRE,3076
1642
1642
  vellum/workflows/state/encoder.py,sha256=TnOQojc5lTQ83g9QbpA4UCqShJvutmTMxbpKt-9gNe4,1911
1643
1643
  vellum/workflows/state/store.py,sha256=uVe-oN73KwGV6M6YLhwZMMUQhzTQomsVfVnb8V91gVo,1147
1644
1644
  vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1670,8 +1670,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1670
1670
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1671
1671
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=tCxrV3QBHL8wfdEO3bvKteDdw32xBlUl1_WxkAwaONw,8344
1672
1672
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1673
- vellum_ai-0.14.33.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1674
- vellum_ai-0.14.33.dist-info/METADATA,sha256=8j_YxkXJ0HIBjUZszx0257w_iMhp8KxdW3Y3pUmabMs,5484
1675
- vellum_ai-0.14.33.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1676
- vellum_ai-0.14.33.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1677
- vellum_ai-0.14.33.dist-info/RECORD,,
1673
+ vellum_ai-0.14.34.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1674
+ vellum_ai-0.14.34.dist-info/METADATA,sha256=1DTJPYOhbXBtorWcrH9e992hHsfsTsvKtleo96NqcaY,5484
1675
+ vellum_ai-0.14.34.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1676
+ vellum_ai-0.14.34.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1677
+ vellum_ai-0.14.34.dist-info/RECORD,,
vellum_cli/pull.py CHANGED
@@ -2,6 +2,7 @@ import io
2
2
  import json
3
3
  import os
4
4
  from pathlib import Path
5
+ from uuid import UUID
5
6
  import zipfile
6
7
  from typing import Optional
7
8
 
@@ -10,8 +11,9 @@ from pydash import snake_case
10
11
 
11
12
  from vellum.client.core.api_error import ApiError
12
13
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
14
+ from vellum.utils.uuid import is_valid_uuid
13
15
  from vellum.workflows.vellum_client import create_vellum_client
14
- from vellum_cli.config import VellumCliConfig, WorkflowConfig, load_vellum_cli_config
16
+ from vellum_cli.config import VellumCliConfig, WorkflowConfig, WorkflowDeploymentConfig, load_vellum_cli_config
15
17
  from vellum_cli.logger import load_cli_logger
16
18
 
17
19
  ERROR_LOG_FILE_NAME = "error.log"
@@ -31,6 +33,7 @@ class RunnerConfig(UniversalBaseModel):
31
33
  class PullContentsMetadata(UniversalBaseModel):
32
34
  label: Optional[str] = None
33
35
  runner_config: Optional[RunnerConfig] = None
36
+ deployment_id: Optional[UUID] = None
34
37
  deployment_name: Optional[str] = None
35
38
 
36
39
 
@@ -79,6 +82,29 @@ def _resolve_workflow_config(
79
82
  pk=workflow_config.workflow_sandbox_id,
80
83
  )
81
84
  elif workflow_deployment:
85
+ if is_valid_uuid(workflow_deployment):
86
+ # name may also be a valid UUID
87
+ workflow_config = next(
88
+ (
89
+ w
90
+ for w in config.workflows
91
+ if w.deployments
92
+ and (
93
+ str(w.deployments[0].id) == workflow_deployment or w.deployments[0].name == workflow_deployment
94
+ )
95
+ ),
96
+ None,
97
+ )
98
+ else:
99
+ workflow_config = next(
100
+ (w for w in config.workflows if w.deployments and w.deployments[0].name == workflow_deployment), None
101
+ )
102
+ if workflow_config:
103
+ return WorkflowConfigResolutionResult(
104
+ workflow_config=workflow_config,
105
+ pk=workflow_deployment,
106
+ )
107
+
82
108
  workflow_config = WorkflowConfig(
83
109
  module="",
84
110
  )
@@ -177,6 +203,24 @@ def pull_command(
177
203
  if not workflow_config.module and pull_contents_metadata.label:
178
204
  workflow_config.module = snake_case(pull_contents_metadata.label)
179
205
 
206
+ # Save or update the deployment info when pulling with --workflow-deployment
207
+ if workflow_deployment:
208
+ workflow_deployment_id = pull_contents_metadata.deployment_id
209
+ existing_deployment = next(
210
+ (d for d in workflow_config.deployments if d.id == workflow_deployment_id), None
211
+ )
212
+
213
+ if existing_deployment:
214
+ if pull_contents_metadata.label:
215
+ existing_deployment.label = pull_contents_metadata.label
216
+ else:
217
+ deployment_config = WorkflowDeploymentConfig(
218
+ id=workflow_deployment_id,
219
+ label=pull_contents_metadata.label,
220
+ name=pull_contents_metadata.deployment_name,
221
+ )
222
+ workflow_config.deployments.append(deployment_config)
223
+
180
224
  if not workflow_config.module:
181
225
  raise ValueError(f"Failed to resolve a module name for Workflow {pk}")
182
226
 
@@ -337,6 +337,7 @@ def test_pull__sandbox_id_with_other_workflow_configured(vellum_client, mock_mod
337
337
  def test_pull__workflow_deployment_with_no_config(vellum_client):
338
338
  # GIVEN a workflow deployment
339
339
  workflow_deployment = "my-deployment"
340
+ deployment_id = str(uuid4())
340
341
 
341
342
  # AND the workflow pull API call returns a zip file
342
343
  vellum_client.workflows.pull.return_value = iter(
@@ -344,7 +345,9 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
344
345
  _zip_file_map(
345
346
  {
346
347
  "workflow.py": "print('hello')",
347
- "metadata.json": json.dumps({"deployment_name": workflow_deployment, "label": "Some Label"}),
348
+ "metadata.json": json.dumps(
349
+ {"deployment_id": deployment_id, "deployment_name": workflow_deployment, "label": "Some Label"}
350
+ ),
348
351
  }
349
352
  )
350
353
  ]
@@ -382,7 +385,15 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
382
385
  "module": "my_deployment",
383
386
  "workflow_sandbox_id": None,
384
387
  "ignore": None,
385
- "deployments": [],
388
+ "deployments": [
389
+ {
390
+ "id": deployment_id,
391
+ "label": "Some Label",
392
+ "name": "my-deployment",
393
+ "description": None,
394
+ "release_tags": None,
395
+ }
396
+ ],
386
397
  "container_image_tag": None,
387
398
  "container_image_name": None,
388
399
  "workspace": "default",
@@ -937,3 +948,313 @@ def test_pull__unauthorized_error_path(vellum_client):
937
948
  # THEN the command returns an error
938
949
  assert result.exit_code == 1
939
950
  assert str(result.exception) == "Please make sure your `VELLUM_API_KEY` environment variable is set correctly."
951
+
952
+
953
+ @pytest.mark.parametrize(
954
+ "workflow_deployment",
955
+ [
956
+ "test-workflow-deployment-id",
957
+ str(uuid4()),
958
+ ],
959
+ )
960
+ def test_pull__workflow_deployment_adds_deployment_to_config(vellum_client, workflow_deployment):
961
+ # GIVEN a workflow deployment ID
962
+ deployment_id = str(uuid4()) # config will always use the deployment_id return from the API
963
+ deployment_name = "Test Deployment"
964
+ deployment_label = "Test Label"
965
+
966
+ # AND the workflow pull API call returns a zip file with metadata
967
+ vellum_client.workflows.pull.return_value = iter(
968
+ [
969
+ _zip_file_map(
970
+ {
971
+ "workflow.py": "print('hello')",
972
+ "metadata.json": json.dumps(
973
+ {
974
+ "deployment_id": deployment_id,
975
+ "deployment_name": deployment_name,
976
+ "label": deployment_label,
977
+ }
978
+ ),
979
+ }
980
+ )
981
+ ]
982
+ )
983
+
984
+ # AND we are currently in a new directory
985
+ current_dir = os.getcwd()
986
+ temp_dir = tempfile.mkdtemp()
987
+ os.chdir(temp_dir)
988
+
989
+ # WHEN the user runs the pull command with the workflow deployment
990
+ runner = CliRunner()
991
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", workflow_deployment])
992
+
993
+ # THEN the command returns successfully
994
+ assert result.exit_code == 0
995
+
996
+ # AND the deployment is saved in the config
997
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
998
+ assert os.path.exists(vellum_lock_json)
999
+ with open(vellum_lock_json) as f:
1000
+ lock_data = json.loads(f.read())
1001
+ assert len(lock_data["workflows"]) == 1
1002
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1003
+ deployment = lock_data["workflows"][0]["deployments"][0]
1004
+ assert str(deployment["id"]) == deployment_id
1005
+ assert deployment["name"] == deployment_name
1006
+ assert deployment["label"] == deployment_label
1007
+
1008
+ os.chdir(current_dir)
1009
+
1010
+
1011
+ def test_pull__workflow_deployment_name_is_uuid(vellum_client):
1012
+ # GIVEN a workflow deployment name that is a valid UUID
1013
+ deployment_id = str(uuid4())
1014
+ deployment_name = str(uuid4())
1015
+
1016
+ # AND an existing configuration with this deployment
1017
+ current_dir = os.getcwd()
1018
+ temp_dir = tempfile.mkdtemp()
1019
+ os.chdir(temp_dir)
1020
+
1021
+ # Create initial config with a deployment
1022
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1023
+ with open(vellum_lock_json, "w") as f:
1024
+ json.dump(
1025
+ {
1026
+ "version": "1.0",
1027
+ "workflows": [
1028
+ {
1029
+ "module": "test_workflow",
1030
+ "workflow_sandbox_id": None,
1031
+ "ignore": None,
1032
+ "deployments": [
1033
+ {
1034
+ "id": deployment_id,
1035
+ "label": "Test Label",
1036
+ "name": deployment_name,
1037
+ "description": None,
1038
+ "release_tags": None,
1039
+ }
1040
+ ],
1041
+ "container_image_name": None,
1042
+ "container_image_tag": None,
1043
+ "workspace": "default",
1044
+ "target_directory": None,
1045
+ }
1046
+ ],
1047
+ "workspaces": [],
1048
+ },
1049
+ f,
1050
+ )
1051
+
1052
+ # AND the workflow pull API call returns a zip file with updated metadata
1053
+ updated_label = "Updated Label"
1054
+ vellum_client.workflows.pull.return_value = iter(
1055
+ [
1056
+ _zip_file_map(
1057
+ {
1058
+ "workflow.py": "print('hello')",
1059
+ "metadata.json": json.dumps(
1060
+ {
1061
+ "deployment_id": deployment_id,
1062
+ "deployment_name": deployment_name,
1063
+ "label": updated_label,
1064
+ }
1065
+ ),
1066
+ }
1067
+ )
1068
+ ]
1069
+ )
1070
+
1071
+ # WHEN the user runs the pull command with the workflow deployment
1072
+ runner = CliRunner()
1073
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", deployment_name])
1074
+
1075
+ # THEN the command returns successfully
1076
+ assert result.exit_code == 0
1077
+
1078
+ # AND the deployment info is updated in the config
1079
+ with open(vellum_lock_json) as f:
1080
+ lock_data = json.loads(f.read())
1081
+ assert len(lock_data["workflows"]) == 1
1082
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1083
+ deployment = lock_data["workflows"][0]["deployments"][0]
1084
+ assert str(deployment["id"]) == deployment_id
1085
+ assert deployment["name"] == deployment_name
1086
+ assert deployment["label"] == updated_label
1087
+
1088
+ os.chdir(current_dir)
1089
+
1090
+
1091
+ @pytest.mark.parametrize("get_identifier", [(lambda d: d), (lambda d: "Test Name")])
1092
+ def test_pull__workflow_deployment_updates_existing_deployment(vellum_client, get_identifier):
1093
+ """
1094
+ This test is to ensure that the deployment info is updated in the config
1095
+ when the user runs the pull command with the workflow deployment
1096
+
1097
+ get_identifier is a function that returns the identifier to use for the deployment
1098
+ it can be the deployment_id or the deployment_name
1099
+ """
1100
+ # GIVEN a workflow deployment id and name
1101
+ deployment_id = str(uuid4())
1102
+ deployment_name = "Test Name"
1103
+
1104
+ # AND an existing configuration with this deployment
1105
+ current_dir = os.getcwd()
1106
+ temp_dir = tempfile.mkdtemp()
1107
+ os.chdir(temp_dir)
1108
+
1109
+ # Create initial config with a deployment
1110
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1111
+ with open(vellum_lock_json, "w") as f:
1112
+ json.dump(
1113
+ {
1114
+ "version": "1.0",
1115
+ "workflows": [
1116
+ {
1117
+ "module": "test_workflow",
1118
+ "workflow_sandbox_id": None,
1119
+ "ignore": None,
1120
+ "deployments": [
1121
+ {
1122
+ "id": deployment_id,
1123
+ "label": "Test Label",
1124
+ "name": deployment_name,
1125
+ "description": None,
1126
+ "release_tags": None,
1127
+ }
1128
+ ],
1129
+ "container_image_name": None,
1130
+ "container_image_tag": None,
1131
+ "workspace": "default",
1132
+ "target_directory": None,
1133
+ }
1134
+ ],
1135
+ "workspaces": [],
1136
+ },
1137
+ f,
1138
+ )
1139
+
1140
+ # AND the workflow pull API call returns a zip file with updated metadata
1141
+ updated_label = "Updated Label"
1142
+ vellum_client.workflows.pull.return_value = iter(
1143
+ [
1144
+ _zip_file_map(
1145
+ {
1146
+ "workflow.py": "print('hello')",
1147
+ "metadata.json": json.dumps(
1148
+ {
1149
+ "deployment_id": deployment_id,
1150
+ "deployment_name": deployment_name,
1151
+ "label": updated_label,
1152
+ }
1153
+ ),
1154
+ }
1155
+ )
1156
+ ]
1157
+ )
1158
+
1159
+ # WHEN the user runs the pull command with the workflow deployment
1160
+ runner = CliRunner()
1161
+ identifier_to_use = get_identifier(deployment_id)
1162
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", identifier_to_use])
1163
+
1164
+ # THEN the command returns successfully
1165
+ assert result.exit_code == 0
1166
+
1167
+ # AND the deployment info is updated in the config
1168
+ with open(vellum_lock_json) as f:
1169
+ lock_data = json.loads(f.read())
1170
+ assert len(lock_data["workflows"]) == 1
1171
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1172
+ deployment = lock_data["workflows"][0]["deployments"][0]
1173
+ assert str(deployment["id"]) == deployment_id
1174
+ assert deployment["name"] == deployment_name
1175
+ assert deployment["label"] == updated_label
1176
+
1177
+ os.chdir(current_dir)
1178
+
1179
+
1180
+ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
1181
+ """
1182
+ This test is to ensure that pulling with id and name will not add a new deployment to the config
1183
+ """
1184
+ # GIVEN a workflow deployment ID
1185
+ deployment_id = str(uuid4()) # config will always use the deployment_id return from the API
1186
+ deployment_name = "Test Deployment"
1187
+ deployment_label = "Test Label"
1188
+
1189
+ # AND the workflow pull API call returns a zip file with metadata
1190
+ vellum_client.workflows.pull.return_value = iter(
1191
+ [
1192
+ _zip_file_map(
1193
+ {
1194
+ "workflow.py": "print('hello')",
1195
+ "metadata.json": json.dumps(
1196
+ {
1197
+ "deployment_id": deployment_id,
1198
+ "deployment_name": deployment_name,
1199
+ "label": deployment_label,
1200
+ }
1201
+ ),
1202
+ }
1203
+ )
1204
+ ]
1205
+ )
1206
+
1207
+ # AND we are currently in a new directory
1208
+ current_dir = os.getcwd()
1209
+ temp_dir = tempfile.mkdtemp()
1210
+ os.chdir(temp_dir)
1211
+
1212
+ # WHEN the user runs the pull command with the workflow deployment
1213
+ runner = CliRunner()
1214
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", deployment_id])
1215
+
1216
+ # THEN the command returns successfully
1217
+ assert result.exit_code == 0
1218
+
1219
+ # AND the deployment is saved in the config
1220
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
1221
+ assert os.path.exists(vellum_lock_json)
1222
+ with open(vellum_lock_json) as f:
1223
+ lock_data = json.loads(f.read())
1224
+ assert len(lock_data["workflows"]) == 1
1225
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1226
+ deployment = lock_data["workflows"][0]["deployments"][0]
1227
+ assert str(deployment["id"]) == deployment_id
1228
+ assert deployment["name"] == deployment_name
1229
+ assert deployment["label"] == deployment_label
1230
+
1231
+ os.chdir(current_dir)
1232
+
1233
+ # AND pull with name will not add a new deployment to the config
1234
+ vellum_client.workflows.pull.return_value = iter(
1235
+ [
1236
+ _zip_file_map(
1237
+ {
1238
+ "workflow.py": "print('hello')",
1239
+ "metadata.json": json.dumps(
1240
+ {
1241
+ "deployment_id": deployment_id,
1242
+ "deployment_name": deployment_name,
1243
+ "label": deployment_label,
1244
+ }
1245
+ ),
1246
+ }
1247
+ )
1248
+ ]
1249
+ )
1250
+
1251
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-deployment", deployment_name])
1252
+ assert result.exit_code == 0
1253
+ with open(vellum_lock_json) as f:
1254
+ lock_data = json.loads(f.read())
1255
+ assert len(lock_data["workflows"][0]["deployments"]) == 1
1256
+ assert lock_data["workflows"][0]["deployments"][0]["id"] == deployment_id
1257
+ assert lock_data["workflows"][0]["deployments"][0]["name"] == deployment_name
1258
+ assert lock_data["workflows"][0]["deployments"][0]["label"] == deployment_label
1259
+
1260
+ os.chdir(current_dir)
@@ -12,8 +12,7 @@ from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
12
12
 
13
13
 
14
14
  def test_load_workflow_event_display_context():
15
- # DEPRECATED: Use `vellum.workflows.events.workflow.WorkflowEventDisplayContext` instead. Will be removed in 0.15.0
16
- from vellum_ee.workflows.display.types import WorkflowEventDisplayContext
15
+ from vellum.workflows.events.workflow import WorkflowEventDisplayContext
17
16
 
18
17
  # We are actually just ensuring there are no circular dependencies when
19
18
  # our Workflow Server imports this class.
@@ -140,3 +139,358 @@ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, int]):
140
139
 
141
140
  # AND we get the code execution result
142
141
  assert event.body.outputs == {"final_output": 5.0}
142
+
143
+
144
+ def test_load_from_module__simple_code_execution_node_with_try(
145
+ vellum_client,
146
+ ):
147
+ # GIVEN a simple Python script
148
+ py_code = """def main() -> int:
149
+ print("Hello")
150
+ return 1
151
+ """
152
+
153
+ # AND a workflow module with only a code execution node
154
+ files = {
155
+ "__init__.py": "",
156
+ "workflow.py": """
157
+ from vellum.workflows import BaseWorkflow
158
+ from vellum.workflows.state import BaseState
159
+
160
+ from .nodes.code_execution_node import CodeExecutionNode
161
+
162
+ class Workflow(BaseWorkflow):
163
+ graph = CodeExecutionNode
164
+
165
+ class Outputs(BaseWorkflow.Outputs):
166
+ final_output = CodeExecutionNode.Outputs.result
167
+ """,
168
+ "nodes/__init__.py": """
169
+ from .code_execution_node import CodeExecutionNode
170
+
171
+ __all__ = ["CodeExecutionNode"]
172
+ """,
173
+ "nodes/code_execution_node/__init__.py": """
174
+ from typing import Union
175
+
176
+ from vellum.workflows.nodes.core import TryNode
177
+ from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
178
+ from vellum.workflows.state import BaseState
179
+
180
+ @TryNode.wrap()
181
+ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, Union[float, int]]):
182
+ filepath = "./script.py"
183
+ code_inputs = {}
184
+ runtime = "PYTHON_3_11_6"
185
+ packages = []
186
+ """,
187
+ "nodes/code_execution_node/script.py": py_code,
188
+ }
189
+
190
+ namespace = str(uuid4())
191
+
192
+ # AND the virtual file loader is registered
193
+ finder = VirtualFileFinder(files, namespace)
194
+ sys.meta_path.append(finder)
195
+
196
+ # AND we know what the Code Execution Node will respond with
197
+ mock_code_execution = CodeExecutorResponse(
198
+ log="hello",
199
+ output=NumberVellumValue(value=1),
200
+ )
201
+ vellum_client.execute_code.return_value = mock_code_execution
202
+
203
+ # WHEN the workflow is loaded
204
+ Workflow = BaseWorkflow.load_from_module(namespace)
205
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
206
+
207
+ # THEN the workflow is successfully initialized
208
+ assert workflow
209
+
210
+ # WHEN we run the workflow
211
+ event = workflow.run()
212
+
213
+ # THEN the execution is fulfilled
214
+ assert event.name == "workflow.execution.fulfilled"
215
+
216
+ # AND we get the code execution result
217
+ assert event.body.outputs == {"final_output": 1.0}
218
+
219
+
220
+ def test_load_from_module__simple_code_execution_node_with_retry(
221
+ vellum_client,
222
+ ):
223
+ # GIVEN a simple Python script
224
+ py_code = """def main() -> int:
225
+ print("Hello")
226
+ return 1
227
+ """
228
+
229
+ # AND a workflow module with only a code execution node wrapped with RetryNode
230
+ files = {
231
+ "__init__.py": "",
232
+ "workflow.py": """
233
+ from vellum.workflows import BaseWorkflow
234
+ from vellum.workflows.state import BaseState
235
+
236
+ from .nodes.code_execution_node import CodeExecutionNode
237
+
238
+ class Workflow(BaseWorkflow):
239
+ graph = CodeExecutionNode
240
+
241
+ class Outputs(BaseWorkflow.Outputs):
242
+ final_output = CodeExecutionNode.Outputs.result
243
+ """,
244
+ "nodes/__init__.py": """
245
+ from .code_execution_node import CodeExecutionNode
246
+
247
+ __all__ = ["CodeExecutionNode"]
248
+ """,
249
+ "nodes/code_execution_node/__init__.py": """
250
+ from typing import Union
251
+
252
+ from vellum.workflows.nodes.core.retry_node.node import RetryNode
253
+ from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
254
+ from vellum.workflows.state import BaseState
255
+
256
+ @RetryNode.wrap(max_attempts=3)
257
+ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, Union[float, int]]):
258
+ filepath = "./script.py"
259
+ code_inputs = {}
260
+ runtime = "PYTHON_3_11_6"
261
+ packages = []
262
+ """,
263
+ "nodes/code_execution_node/script.py": py_code,
264
+ }
265
+
266
+ namespace = str(uuid4())
267
+
268
+ # AND the virtual file loader is registered
269
+ finder = VirtualFileFinder(files, namespace)
270
+ sys.meta_path.append(finder)
271
+
272
+ # AND we know what the Code Execution Node will respond with
273
+ mock_code_execution = CodeExecutorResponse(
274
+ log="hello",
275
+ output=NumberVellumValue(value=1),
276
+ )
277
+ vellum_client.execute_code.return_value = mock_code_execution
278
+
279
+ # WHEN the workflow is loaded
280
+ Workflow = BaseWorkflow.load_from_module(namespace)
281
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
282
+
283
+ # THEN the workflow is successfully initialized
284
+ assert workflow
285
+
286
+ # WHEN we run the workflow
287
+ event = workflow.run()
288
+
289
+ # THEN the execution is fulfilled
290
+ assert event.name == "workflow.execution.fulfilled"
291
+
292
+ # AND we get the code execution result
293
+ assert event.body.outputs == {"final_output": 1.0}
294
+
295
+
296
+ def test_load_from_module__code_execution_within_subworkflow(
297
+ vellum_client,
298
+ ):
299
+ # GIVEN a simple Python script
300
+ py_code = """def main() -> int:
301
+ print("Hello")
302
+ return 1
303
+ """
304
+
305
+ # AND a workflow module with a subworkflow containing a code execution node
306
+ files = {
307
+ "__init__.py": "",
308
+ "workflow.py": """
309
+ from vellum.workflows import BaseWorkflow
310
+ from vellum.workflows.state import BaseState
311
+
312
+ from .nodes.subworkflow import Subworkflow
313
+
314
+ class Workflow(BaseWorkflow):
315
+ graph = Subworkflow
316
+
317
+ class Outputs(BaseWorkflow.Outputs):
318
+ final_output = Subworkflow.Outputs.result
319
+ """,
320
+ "nodes/__init__.py": """
321
+ from .subworkflow import Subworkflow
322
+
323
+ __all__ = ["Subworkflow"]
324
+ """,
325
+ "nodes/subworkflow/__init__.py": """
326
+ from vellum.workflows.nodes.displayable import InlineSubworkflowNode
327
+
328
+ from .workflow import SubworkflowWorkflow
329
+
330
+ class Subworkflow(InlineSubworkflowNode):
331
+ subworkflow = SubworkflowWorkflow
332
+ """,
333
+ "nodes/subworkflow/nodes/__init__.py": """
334
+ from .code_execution_node import CodeExecutionNode
335
+
336
+ __all__ = ["CodeExecutionNode"]
337
+ """,
338
+ "nodes/subworkflow/nodes/code_execution_node/__init__.py": """
339
+ from typing import Union
340
+
341
+ from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
342
+ from vellum.workflows.state import BaseState
343
+
344
+ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, Union[float, int]]):
345
+ filepath = "./script.py"
346
+ code_inputs = {}
347
+ runtime = "PYTHON_3_11_6"
348
+ packages = []
349
+ """,
350
+ "nodes/subworkflow/nodes/code_execution_node/script.py": py_code,
351
+ "nodes/subworkflow/workflow.py": """
352
+ from vellum.workflows import BaseWorkflow
353
+
354
+ from .nodes.code_execution_node import CodeExecutionNode
355
+
356
+ class SubworkflowWorkflow(BaseWorkflow):
357
+ graph = CodeExecutionNode
358
+
359
+ class Outputs(BaseWorkflow.Outputs):
360
+ result = CodeExecutionNode.Outputs.result
361
+ """,
362
+ }
363
+
364
+ namespace = str(uuid4())
365
+
366
+ # AND the virtual file loader is registered
367
+ finder = VirtualFileFinder(files, namespace)
368
+ sys.meta_path.append(finder)
369
+
370
+ # AND we know what the Code Execution Node will respond with
371
+ mock_code_execution = CodeExecutorResponse(
372
+ log="hello",
373
+ output=NumberVellumValue(value=1),
374
+ )
375
+ vellum_client.execute_code.return_value = mock_code_execution
376
+
377
+ # WHEN the workflow is loaded
378
+ Workflow = BaseWorkflow.load_from_module(namespace)
379
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
380
+
381
+ # THEN the workflow is successfully initialized
382
+ assert workflow
383
+
384
+ # WHEN we run the workflow
385
+ event = workflow.run()
386
+
387
+ # THEN the execution is fulfilled
388
+ assert event.name == "workflow.execution.fulfilled"
389
+
390
+ # AND we get the code execution result from the subworkflow
391
+ assert event.body.outputs == {"final_output": 1.0}
392
+
393
+
394
+ def test_load_from_module__code_execution_within_map_node(
395
+ vellum_client,
396
+ ):
397
+ # GIVEN a simple Python script
398
+ py_code = """def main() -> int:
399
+ print("Hello")
400
+ return 1
401
+ """
402
+
403
+ # AND a workflow module with a map node containing a code execution node
404
+ files = {
405
+ "__init__.py": "",
406
+ "workflow.py": """
407
+ from vellum.workflows import BaseWorkflow
408
+ from vellum.workflows.state import BaseState
409
+
410
+ from .nodes.map_node import MapNode
411
+
412
+ class Workflow(BaseWorkflow):
413
+ graph = MapNode
414
+
415
+ class Outputs(BaseWorkflow.Outputs): # noqa: W293
416
+ results = MapNode.Outputs.final_output
417
+ """,
418
+ "nodes/__init__.py": """
419
+ from .map_node import MapNode
420
+
421
+ __all__ = ["MapNode"]
422
+ """,
423
+ "nodes/map_node/__init__.py": """
424
+ from vellum.workflows.nodes.core.map_node import MapNode as BaseMapNode
425
+
426
+ from .workflow import MapNodeWorkflow
427
+
428
+ class MapNode(BaseMapNode):
429
+ items = ["foo", "bar", "baz"]
430
+ subworkflow = MapNodeWorkflow
431
+ max_concurrency = 4
432
+ """,
433
+ "nodes/map_node/nodes/__init__.py": """
434
+ from .code_execution_node import CodeExecutionNode
435
+
436
+ __all__ = ["CodeExecutionNode"]
437
+ """,
438
+ "nodes/map_node/nodes/code_execution_node/__init__.py": """
439
+ from typing import Union
440
+
441
+ from vellum.workflows.nodes.displayable import CodeExecutionNode as BaseCodeExecutionNode
442
+ from vellum.workflows.state import BaseState
443
+
444
+ class CodeExecutionNode(BaseCodeExecutionNode[BaseState, Union[float, int]]):
445
+ filepath = "./script.py"
446
+ code_inputs = {}
447
+ runtime = "PYTHON_3_11_6"
448
+ packages = []
449
+ """,
450
+ "nodes/map_node/nodes/code_execution_node/script.py": py_code,
451
+ "nodes/map_node/workflow.py": """
452
+ from vellum.workflows import BaseWorkflow
453
+ from vellum.workflows.state import BaseState
454
+
455
+ from .nodes.code_execution_node import CodeExecutionNode
456
+
457
+ class MapNodeInputs:
458
+ item: str
459
+ index: int
460
+
461
+ class MapNodeWorkflow(BaseWorkflow):
462
+ graph = CodeExecutionNode
463
+
464
+ class Outputs(BaseWorkflow.Outputs): # noqa: W293
465
+ final_output = CodeExecutionNode.Outputs.result
466
+ """,
467
+ }
468
+
469
+ namespace = str(uuid4())
470
+
471
+ # AND the virtual file loader is registered
472
+ finder = VirtualFileFinder(files, namespace)
473
+ sys.meta_path.append(finder)
474
+
475
+ # AND we know what the Code Execution Node will respond with
476
+ mock_code_execution = CodeExecutorResponse(
477
+ log="hello",
478
+ output=NumberVellumValue(value=3),
479
+ )
480
+ vellum_client.execute_code.return_value = mock_code_execution
481
+
482
+ # WHEN the workflow is loaded
483
+ Workflow = BaseWorkflow.load_from_module(namespace)
484
+ workflow = Workflow(context=WorkflowContext(generated_files=files))
485
+
486
+ # THEN the workflow is successfully initialized
487
+ assert workflow
488
+
489
+ # WHEN we run the workflow
490
+ event = workflow.run()
491
+
492
+ # THEN the execution is fulfilled
493
+ assert event.name == "workflow.execution.fulfilled"
494
+
495
+ # AND we get the map node results as a list
496
+ assert event.body.outputs == {"results": [1.0, 1.0, 1.0]}