vellum-ai 0.14.14__py3-none-any.whl → 0.14.15__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.
Files changed (23) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/client/core/pydantic_utilities.py +4 -5
  3. vellum/client/types/logical_operator.py +1 -0
  4. vellum/workflows/nodes/core/retry_node/tests/test_node.py +23 -0
  5. vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +29 -0
  6. vellum/workflows/nodes/displayable/bases/api_node/node.py +2 -2
  7. {vellum_ai-0.14.14.dist-info → vellum_ai-0.14.15.dist-info}/METADATA +2 -2
  8. {vellum_ai-0.14.14.dist-info → vellum_ai-0.14.15.dist-info}/RECORD +23 -23
  9. vellum_ee/workflows/display/nodes/base_node_display.py +8 -5
  10. vellum_ee/workflows/display/nodes/base_node_vellum_display.py +1 -4
  11. vellum_ee/workflows/display/nodes/get_node_display_class.py +34 -8
  12. vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +55 -1
  13. vellum_ee/workflows/display/nodes/vellum/retry_node.py +1 -54
  14. vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -8
  15. vellum_ee/workflows/display/nodes/vellum/try_node.py +1 -42
  16. vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +47 -10
  17. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +67 -0
  18. vellum_ee/workflows/display/utils/vellum.py +3 -0
  19. vellum_ee/workflows/display/workflows/base_workflow_display.py +3 -6
  20. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +52 -0
  21. {vellum_ai-0.14.14.dist-info → vellum_ai-0.14.15.dist-info}/LICENSE +0 -0
  22. {vellum_ai-0.14.14.dist-info → vellum_ai-0.14.15.dist-info}/WHEEL +0 -0
  23. {vellum_ai-0.14.14.dist-info → vellum_ai-0.14.15.dist-info}/entry_points.txt +0 -0
@@ -18,7 +18,7 @@ class BaseClientWrapper:
18
18
  headers: typing.Dict[str, str] = {
19
19
  "X-Fern-Language": "Python",
20
20
  "X-Fern-SDK-Name": "vellum-ai",
21
- "X-Fern-SDK-Version": "0.14.14",
21
+ "X-Fern-SDK-Version": "0.14.15",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -133,12 +133,11 @@ class UniversalBaseModel(pydantic.BaseModel):
133
133
  #
134
134
  # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models
135
135
  # that we have less control over, and this is less intrusive than custom serializers for now.
136
- kwargs = {
137
- **kwargs,
138
- "warnings": False,
139
- }
140
-
141
136
  if IS_PYDANTIC_V2:
137
+ kwargs = {
138
+ **kwargs,
139
+ "warnings": False,
140
+ }
142
141
  kwargs_with_defaults_exclude_unset: typing.Any = {
143
142
  **kwargs,
144
143
  "by_alias": True,
@@ -26,6 +26,7 @@ LogicalOperator = typing.Union[
26
26
  "notBlank",
27
27
  "coalesce",
28
28
  "accessField",
29
+ "parseJson",
29
30
  "and",
30
31
  "or",
31
32
  ],
@@ -33,6 +33,29 @@ def test_retry_node__retry_on_error_code__successfully_retried():
33
33
  assert outputs.execution_count == 3
34
34
 
35
35
 
36
+ def test_retry_node__retry_on_error_code_all():
37
+ # GIVEN a retry node that is configured to retry on all errors
38
+ @RetryNode.wrap(max_attempts=3)
39
+ class TestNode(BaseNode):
40
+ attempt_number = RetryNode.SubworkflowInputs.attempt_number
41
+
42
+ class Outputs(BaseOutputs):
43
+ execution_count: int
44
+
45
+ def run(self) -> Outputs:
46
+ if self.attempt_number < 3:
47
+ raise NodeException(message="This will be retried", code=WorkflowErrorCode.PROVIDER_ERROR)
48
+
49
+ return self.Outputs(execution_count=self.attempt_number)
50
+
51
+ # WHEN the node is run and throws a None
52
+ node = TestNode(state=BaseState())
53
+ outputs = node.run()
54
+
55
+ # THEN the exception is retried
56
+ assert outputs.execution_count == 3
57
+
58
+
36
59
  def test_retry_node__retry_on_error_code__missed():
37
60
  # GIVEN a retry node that is configured to retry on PROVIDER_ERROR
38
61
  @RetryNode.wrap(max_attempts=3, retry_on_error_code=WorkflowErrorCode.PROVIDER_ERROR)
@@ -64,3 +64,32 @@ def test_api_node_raises_error_when_api_call_fails(vellum_client):
64
64
 
65
65
  # AND the API call should have been made
66
66
  assert vellum_client.execute_api.call_count == 1
67
+
68
+
69
+ def test_api_node_defaults_to_get_method(vellum_client):
70
+ # GIVEN a successful API response
71
+ vellum_client.execute_api.return_value = ExecuteApiResponse(
72
+ status_code=200,
73
+ text='{"status": 200, "data": [1, 2, 3]}',
74
+ json_={"data": [1, 2, 3]},
75
+ headers={"X-Response-Header": "bar"},
76
+ )
77
+
78
+ # AND an API node without a method specified
79
+ class SimpleAPINodeWithoutMethod(APINode):
80
+ authorization_type = AuthorizationType.BEARER_TOKEN
81
+ url = "https://api.vellum.ai"
82
+ headers = {
83
+ "X-Test-Header": "foo",
84
+ }
85
+ bearer_token_value = VellumSecret(name="secret")
86
+
87
+ node = SimpleAPINodeWithoutMethod()
88
+
89
+ # WHEN we run the node
90
+ node.run()
91
+
92
+ # THEN the API call should be made with GET method
93
+ assert vellum_client.execute_api.call_count == 1
94
+ method = vellum_client.execute_api.call_args.kwargs["method"]
95
+ assert method == APIRequestMethod.GET.value
@@ -29,7 +29,7 @@ class BaseAPINode(BaseNode, Generic[StateType]):
29
29
  merge_behavior = MergeBehavior.AWAIT_ANY
30
30
 
31
31
  url: str
32
- method: APIRequestMethod
32
+ method: Optional[APIRequestMethod] = APIRequestMethod.GET
33
33
  data: Optional[str] = None
34
34
  json: Optional[Json] = None
35
35
  headers: Optional[Dict[str, Union[str, VellumSecret]]] = None
@@ -45,8 +45,8 @@ class BaseAPINode(BaseNode, Generic[StateType]):
45
45
 
46
46
  def _run(
47
47
  self,
48
- method: APIRequestMethod,
49
48
  url: str,
49
+ method: Optional[APIRequestMethod] = APIRequestMethod.GET,
50
50
  data: Optional[Union[str, Any]] = None,
51
51
  json: Any = None,
52
52
  headers: Any = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.14
3
+ Version: 0.14.15
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -33,7 +33,7 @@ Requires-Dist: pydantic-core (>=2.18.2,<3.0.0)
33
33
  Requires-Dist: pydash (==7.0.6)
34
34
  Requires-Dist: python-dotenv (==1.0.1)
35
35
  Requires-Dist: pytz (==2025.1)
36
- Requires-Dist: pyyaml (==6.0.1)
36
+ Requires-Dist: pyyaml (==6.0.2)
37
37
  Requires-Dist: requests (==2.32.3)
38
38
  Requires-Dist: tomli (==2.0.2)
39
39
  Requires-Dist: typing_extensions (>=4.0.0)
@@ -23,16 +23,16 @@ vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
23
23
  vellum_ee/workflows/display/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  vellum_ee/workflows/display/base.py,sha256=ak29FIsawhaFa9_paZUHThlZRFJ1xB486JWKuSt1PYY,1965
25
25
  vellum_ee/workflows/display/nodes/__init__.py,sha256=436iSAh_Ex5tC68oEYvNgPu05ZVIAVXnS4PKGrQeZ0Y,321
26
- vellum_ee/workflows/display/nodes/base_node_display.py,sha256=p6orKsqEo8u6PWDbpp60D_E4UtjWNzWI8aVe8aknpyc,17097
27
- vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=pLO0dORfRu--Ne9NgoyFT_CNjfpr5fGCsgbsMkUF5GM,2845
28
- vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=67J_TGFNoISq8wZqOBCu5BNMY4kpQBq3Lt65FPH3Gt0,1594
26
+ vellum_ee/workflows/display/nodes/base_node_display.py,sha256=pYmIdmC2dxLyRlZEBlgARIJMLcBX3GxZPpWZISv5Bbg,17170
27
+ vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=ZLKQ8Xa3h9nGkj4t4V_7OeU7CBFWY3gXB9CkaCLOhEk,2699
28
+ vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=RWCGm29-Tabi-qgVEIi_sdTWBv4bEzxAgwAoYI5T-Cc,2566
29
29
  vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=QqR3Ly0RNrXwOeLdW5nERDFt0gRPf76n1bPES6o5UN4,1093
31
31
  vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6mLhstQAvEACbGk,247
32
32
  vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
33
33
  vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nUIgH2s0-7IbQRNrBhLPyRNe8YIrx3Yo9HeeW-aXXFk,1668
34
34
  vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=hoV-cUtS6H9kmRQXHd2py95GRWI_dAnnaPwvlNBkDOQ,8571
35
- vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py,sha256=UlfX8ifleaAaRCmGlj6XNubEMJnHOgCXSBRk6LAZw38,1995
35
+ vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py,sha256=t-KSaai6cXRLkNkzCvEbM5SHh03B9fqPwVTH-Gei_-0,4419
36
36
  vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=z00Z3L0d4PsUQo4S8FRDTtOFLtjdi17TJbatNVF4nM8,4288
37
37
  vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=ybLIa4uclqVIy3VAQvI1ivg2tnK5Ug_1R5a69DFqL7E,11104
38
38
  vellum_ee/workflows/display/nodes/vellum/error_node.py,sha256=I1Jkp2htRINJATtv1e-zs9BrReFX842djpiVgBPHDYg,2186
@@ -44,27 +44,27 @@ vellum_ee/workflows/display/nodes/vellum/map_node.py,sha256=8CPnn06HIBxBOiECevUf
44
44
  vellum_ee/workflows/display/nodes/vellum/merge_node.py,sha256=HkNMgdQELiON42jdO-xDLmqrEKdGx1RVqrz2DXNTLS8,3239
45
45
  vellum_ee/workflows/display/nodes/vellum/note_node.py,sha256=TMb8txILu2uWjzoxaghjgjlzeBAgzn4vkP_8zSh2qoE,1151
46
46
  vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py,sha256=LFjLUrH6sJ4czPnExdRqFr0PB_yKBMLXLvK5GAzIAgc,3273
47
- vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=-eH41LUcpUeZ8Jt5f40-e7bn2nymeKnsYsmuprVSo7g,5805
47
+ vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=LgokATi7sSS38Fil-XjqoR4t7AMOJ-GzXRw6p606Svo,3397
48
48
  vellum_ee/workflows/display/nodes/vellum/search_node.py,sha256=TxcAGZDl_hvJ7Y1hUi9YVEVrj9Ie0hKkASdpfRL4_cs,9227
49
49
  vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py,sha256=62baAElKoRKIoba0lLhnrXGWWx96B73VxKGxh7BaIxc,2612
50
- vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=JVIMPR3WpveOCWZubHKZkE04mavnTdb_9QY_r3XliRg,3424
50
+ vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=wP-ZAZq6ZmkS1fj0WkFtRkYqi8c6MWKLh4CEw8JD9mg,3207
51
51
  vellum_ee/workflows/display/nodes/vellum/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  vellum_ee/workflows/display/nodes/vellum/tests/test_error_node.py,sha256=ulrpoYUW-5kIxfG4Lf5F2p0k_EoYKhmahEbF3P_eruM,1648
53
53
  vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py,sha256=bg9INsXiWfyK047u8TD1oEOFYrqDq8GC7Hvgz69n7BE,1988
54
54
  vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py,sha256=NuIw8Yb42KUdoGi3Ur8_7VPg50IC4hNrwAkCociwqNk,2091
55
55
  vellum_ee/workflows/display/nodes/vellum/tests/test_try_node.py,sha256=mtzB8LJlFCHVFM4H5AanLp29gQfaVmnN4A4iaRGJHoI,2427
56
56
  vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=4YUaTeD_OWF-UaPMyOTBTu9skGC1jgSHlAYrzbH7Z04,5039
57
- vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=HBfGz4yt9GlmMW9JxzaCacPnHBDNIeXE8Jhqr9DqLLw,6191
57
+ vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=0ipBZMYm521tuwefQFgHOvTyTgiWVTkzxpQtnnmOAI0,4203
58
58
  vellum_ee/workflows/display/nodes/vellum/utils.py,sha256=F_0BrlSszllK_BhryPbojIleLq2dGXOfQD1rVp3fNFg,4733
59
59
  vellum_ee/workflows/display/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=cdpUoDNli8ULlNTrU6rnT4TZAIR8mzsG9ZbLrikPybU,8518
60
+ vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=VD-4USiRlCcdC3Qe9WfdkxwFdircai0vqvuZCbELR84,9556
61
61
  vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
63
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=A1-tIpC5KIKG9JA_rkd1nLS8zUG3Kb4QiVdvb3boFxE,2509
64
64
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=mFmxXfnUPrwndaBurW9E-VSBUQjF3cGv3JpRuNmwWh8,15475
65
65
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=1cszL6N6FNGVm61MOa7AEiHnF0QjZWqDQuPOp4yiG94,18277
66
66
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=-12ZkZb3f5gyoNASV2yeQtMo5HmNsVEo8nXwL6IC-I8,6261
67
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=6th6kCwzql6lddjkTQx4Jbvvs4ChqtJwctW-B4QuBhI,37352
67
+ vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=FD94h96w5wWYlLVC8_tqr2aUHPyO2UQh4f03jDX4re4,39783
68
68
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py,sha256=EbVgg_3_ipTt3MOop4RARX0fmNjwqZtkhIXzx9nGw7Y,4487
69
69
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py,sha256=IRazH2QR6F8RGqNemEnHueyj5DtEa6rFTYhT16S4jI8,15917
70
70
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py,sha256=V__y7uu-dy6TJjPeu4UDvaoO2yYwBRbPiW9uJdzWRx4,29828
@@ -87,12 +87,12 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_n
87
87
  vellum_ee/workflows/display/types.py,sha256=ixfmcQn51Rhsm4_0hWfG0_WpzLE89ZrDZpeYBklsP1Q,2592
88
88
  vellum_ee/workflows/display/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  vellum_ee/workflows/display/utils/expressions.py,sha256=9FpOslDI-RCR5m4TgAu9KCHh4aTVnh7CHR2ykyMUDw0,1151
90
- vellum_ee/workflows/display/utils/vellum.py,sha256=UjK_RxnSEmlIu9klGCPWU5RAQBmgZ7cRbRdgxaTbubE,8081
90
+ vellum_ee/workflows/display/utils/vellum.py,sha256=EVPQUSsZ3OIeLTEbV6LHPor37t9fnj9kJxDqP4PmTLQ,8234
91
91
  vellum_ee/workflows/display/vellum.py,sha256=7mqQaKZPPrLMcXSAQkPIxCy5x8HkKs5PbCu3GRaC2o8,8507
92
92
  vellum_ee/workflows/display/workflows/__init__.py,sha256=kapXsC67VJcgSuiBMa86FdePG5A9kMB5Pi4Uy1O2ob4,207
93
- vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=yDjMk2tml2TzitcFdP5M7PFXm4G_Bz6aYLYby7riXGQ,20137
93
+ vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=QKMSmV--UoE8L7pYmKvZqtaatygrxmSm9PDSH71Yr0Y,19937
94
94
  vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=kp0u8LN_2IwshLrhMImhpZx1hRyAcD5gXY-kDuuaGMQ,1269
95
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=JRTTM8gX182pBMP5eI_-B_X7LaK0kiSS07KqDdIfrQc,4962
95
+ vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=TzLziIh1fJ429LRUYoe88yd_HZflEQAw60TW8PAvvnk,6917
96
96
  vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=mbAzCpswOek34ITeTkesbVreCXpulj4NFjIg3RcdVZ8,18243
97
97
  vellum_ee/workflows/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  vellum_ee/workflows/server/virtual_file_loader.py,sha256=X_DdNK7MfyOjKWekk6YQpOSCT6klKcdjT6nVJcBH1sM,1481
@@ -123,12 +123,12 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
123
123
  vellum/client/__init__.py,sha256=tKtdM1_GqmGq1gpi9ydWD_T-MM7fPn8QdHh8ww19cNI,117564
124
124
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
125
125
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
126
- vellum/client/core/client_wrapper.py,sha256=F65g9wG6SXfGPkAao7k0GN7doBs1E434iyOswOpG3iE,1869
126
+ vellum/client/core/client_wrapper.py,sha256=x6cZlx3EA5bHNlGRnX0klOVCrqkvqWv_oUW4xjYEehU,1869
127
127
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
128
128
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
129
129
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
130
130
  vellum/client/core/jsonable_encoder.py,sha256=qaF1gtgH-kQZb4kJskETwcCsOPUof-NnYVdszHkb-dM,3656
131
- vellum/client/core/pydantic_utilities.py,sha256=vPw05-aAxUB_jinMTmP7m8dAjRurZ8XqNZruXZdWORs,12056
131
+ vellum/client/core/pydantic_utilities.py,sha256=6ev3gtER-hjlq7PcPL9XT_YSCdgyCE8ZKHJ9Uc-gHIg,12071
132
132
  vellum/client/core/query_encoder.py,sha256=ekulqNd0j8TgD7ox-Qbz7liqX8-KP9blvT9DsRCenYM,2144
133
133
  vellum/client/core/remove_none_from_dict.py,sha256=EU9SGgYidWq7SexuJbNs4-PZ-5Bl3Vppd864mS6vQZw,342
134
134
  vellum/client/core/request_options.py,sha256=5cCGt5AEGgtP5xifDl4oVQUmSjlIA8FmRItAlJawM18,1417
@@ -367,7 +367,7 @@ vellum/client/types/json_input.py,sha256=ZUA2O9YueBCx0IMMdB8uYNSWJiSDZxMm5ogwbwC
367
367
  vellum/client/types/json_input_request.py,sha256=x5sA-VXxF4QH-98xRcIKPZhsMVbnJNUQofiUQqyfGk4,768
368
368
  vellum/client/types/json_vellum_value.py,sha256=8irlw6NkRRVafysfTc1Q5BFFhRrWJYzdwrDYTdJK4JY,689
369
369
  vellum/client/types/json_vellum_value_request.py,sha256=IUlkdwFGgBeLl9sCmAJhoaxomWiEMpWgRcLa_WnlK8g,696
370
- vellum/client/types/logical_operator.py,sha256=3IJNJf8Esrq8-RzcgUJgeM6qns1RHdk01DG7gYs3Az8,596
370
+ vellum/client/types/logical_operator.py,sha256=_3-gBnfsz4g5-sw5_ThRuS0NdNLQ2n2vV2Dl1RqbXe4,617
371
371
  vellum/client/types/logprobs_enum.py,sha256=D_458cZX2CAb6dX_ovrQ6HARlJkYcZRadKwsi1Cr-JM,151
372
372
  vellum/client/types/map_node_result.py,sha256=e2YqEP-aKig2TbbZIlfn5LCeVEVhEJJBR7o7xTBKXaY,752
373
373
  vellum/client/types/map_node_result_data.py,sha256=3xJXC1JrS9lo3-3_u1S79sYwLxMknNntDyspN24vQzY,699
@@ -1380,7 +1380,7 @@ vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=uMR0AyIFn539LqTKH
1380
1380
  vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
1381
1381
  vellum/workflows/nodes/core/retry_node/node.py,sha256=Vt3fx4G-DRIb9a-IHIUfaAclgfbzOPEQVkcumwhl9HE,4355
1382
1382
  vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1383
- vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=RM_OHwxrHwyxvlQQBJPqVBxpedFuWQ9h2-Xa3kP75sc,4399
1383
+ vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=fNgDufkIsrTC-6ftvogqSpWhqqBj9iNESdfK19B1Yx0,5159
1384
1384
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1385
1385
  vellum/workflows/nodes/core/templating_node/node.py,sha256=-JIqLUv6Xpx_LTVZt7whQ2X2VatgHDdTxjMrz64luEs,3721
1386
1386
  vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=MHofz-BwAgt7EXkab8VIyacYznDEIJ7Er7MJUaxNQQo,9614
@@ -1392,10 +1392,10 @@ vellum/workflows/nodes/displayable/__init__.py,sha256=6F_4DlSwvHuilWnIalp8iDjjDX
1392
1392
  vellum/workflows/nodes/displayable/api_node/__init__.py,sha256=MoxdQSnidIj1Nf_d-hTxlOxcZXaZnsWFDbE-PkTK24o,56
1393
1393
  vellum/workflows/nodes/displayable/api_node/node.py,sha256=QdpsyGVxo5PcN8nwGZpcpW_YMKHr3_VvmbK1BlrdOFk,2547
1394
1394
  vellum/workflows/nodes/displayable/api_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1395
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=RtQWnFfq0HNqY2A6c5SdwHl8A91K4Vq8un5UJwwnkSw,2180
1395
+ vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=fiskhfcI4c6CxFlbSWb1JKsuq-98zAeUWOExc848ALw,3130
1396
1396
  vellum/workflows/nodes/displayable/bases/__init__.py,sha256=0mWIx3qUrzllV7jqt7wN03vWGMuI1WrrLZeMLT2Cl2c,304
1397
1397
  vellum/workflows/nodes/displayable/bases/api_node/__init__.py,sha256=1jwx4WC358CLA1jgzl_UD-rZmdMm2v9Mps39ndwCD7U,64
1398
- vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256=-LOKjU_rY1UWgD0DS5LJwAClBI8N7zrdmwigE3y5rhc,4000
1398
+ vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256=Ev_So7D_4Qfvl2_E8veVfxAxWfbJIA2ujyW5istLg5I,4066
1399
1399
  vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org3xTvgp1pA0uUXFfnJr29D3HzCey2lEdYF4zbIUgo,70
1400
1400
  vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=nvhoWb8EyRlgtyotYp-wh194n30yQP81UnOH_a8FghY,3140
1401
1401
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py,sha256=Hl35IAoepRpE-j4cALaXVJIYTYOF3qszyVbxTj4kS1s,82
@@ -1512,8 +1512,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1512
1512
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1513
1513
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=NRteiICyJvDM5zrtUfq2fZoXcGQVaWC9xmNlLLVW0cU,7979
1514
1514
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1515
- vellum_ai-0.14.14.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1516
- vellum_ai-0.14.14.dist-info/METADATA,sha256=UKCcEJ4XtYH1PteNKqVZjzrWn3-WMYYFFEl9SLs_lF4,5408
1517
- vellum_ai-0.14.14.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1518
- vellum_ai-0.14.14.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1519
- vellum_ai-0.14.14.dist-info/RECORD,,
1515
+ vellum_ai-0.14.15.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1516
+ vellum_ai-0.14.15.dist-info/METADATA,sha256=8TM16vJdsQpUXVDgasscuRmbLESHxIgmMj8QlKoLUQU,5408
1517
+ vellum_ai-0.14.15.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1518
+ vellum_ai-0.14.15.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1519
+ vellum_ai-0.14.15.dist-info/RECORD,,
@@ -4,6 +4,7 @@ from uuid import UUID
4
4
  from typing import (
5
5
  TYPE_CHECKING,
6
6
  Any,
7
+ ClassVar,
7
8
  Dict,
8
9
  ForwardRef,
9
10
  Generic,
@@ -59,17 +60,20 @@ _NodeDisplayAttrType = TypeVar("_NodeDisplayAttrType")
59
60
  class BaseNodeDisplayMeta(type):
60
61
  def __new__(mcs, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any]) -> Any:
61
62
  cls = super().__new__(mcs, name, bases, dct)
63
+ return cls.__annotate_node__()
64
+
65
+ def __annotate_node__(cls):
62
66
  base_node_display_class = cast(Type["BaseNodeDisplay"], cls)
63
67
  node_class = base_node_display_class.infer_node_class()
64
68
  if not issubclass(node_class, BaseNode):
65
69
  return cls
66
70
 
67
- display_node_id = dct.get("node_id")
71
+ display_node_id = getattr(cls, "node_id", None)
68
72
  if isinstance(display_node_id, UUID):
69
73
  # Display classes are able to override the id of the node class it's parameterized by
70
74
  node_class.__id__ = display_node_id
71
75
 
72
- output_display = dct.get("output_display")
76
+ output_display = getattr(cls, "output_display", None)
73
77
  if isinstance(output_display, dict):
74
78
  # And the node class' output ids
75
79
  for reference, node_output_display in output_display.items():
@@ -86,6 +90,7 @@ class BaseNodeDisplayMeta(type):
86
90
  class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
87
91
  output_display: Dict[OutputReference, NodeOutputDisplay] = {}
88
92
  port_displays: Dict[Port, PortDisplayOverrides] = {}
93
+ node_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
89
94
 
90
95
  # Used to store the mapping between node types and their display classes
91
96
  _node_display_registry: Dict[Type[NodeType], Type["BaseNodeDisplay"]] = {}
@@ -326,6 +331,7 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
326
331
  IsNotNilExpression,
327
332
  IsUndefinedExpression,
328
333
  IsNotUndefinedExpression,
334
+ ParseJsonExpression,
329
335
  ),
330
336
  ):
331
337
  lhs = self.serialize_value(display_context, condition._expression)
@@ -403,9 +409,6 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
403
409
  "node_id": str(node_class_display.node_id),
404
410
  }
405
411
 
406
- if isinstance(value, ParseJsonExpression):
407
- raise ValueError("ParseJsonExpression is not supported in the UI")
408
-
409
412
  if not isinstance(value, BaseDescriptor):
410
413
  vellum_value = primitive_to_vellum_value(value)
411
414
  return {
@@ -1,5 +1,5 @@
1
1
  from uuid import UUID
2
- from typing import ClassVar, Dict, Optional, Union
2
+ from typing import ClassVar, Dict, Optional
3
3
 
4
4
  from vellum.workflows.nodes.utils import get_unadorned_node
5
5
  from vellum.workflows.ports import Port
@@ -17,9 +17,6 @@ class BaseNodeVellumDisplay(BaseNodeDisplay[NodeType]):
17
17
  # Used to explicitly set the target handle id for a node
18
18
  target_handle_id: ClassVar[Optional[UUID]] = None
19
19
 
20
- # Used to explicitly set the node input ids by name for a node
21
- node_input_ids_by_name: ClassVar[Dict[str, Union[UUID, str]]] = {}
22
-
23
20
  def _get_node_display_uuid(self, attribute: str) -> UUID:
24
21
  explicit_value = self._get_explicit_node_display_attr(attribute, UUID)
25
22
  return explicit_value if explicit_value else uuid4_from_hash(f"{self.node_id}|{attribute}")
@@ -1,7 +1,10 @@
1
1
  import types
2
- from typing import TYPE_CHECKING, Optional, Type
2
+ from uuid import UUID
3
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Type
3
4
 
5
+ from vellum.workflows.descriptors.base import BaseDescriptor
4
6
  from vellum.workflows.types.generics import NodeType
7
+ from vellum.workflows.utils.uuids import uuid4_from_hash
5
8
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
6
9
 
7
10
  if TYPE_CHECKING:
@@ -26,16 +29,39 @@ def get_node_display_class(
26
29
 
27
30
  # `base_node_display_class` is always a Generic class, so it's safe to index into it
28
31
  NodeDisplayBaseClass = base_node_display_class[node_class] # type: ignore[index]
32
+
33
+ def _get_node_input_ids_by_ref(path: str, inst: Any):
34
+ if isinstance(inst, dict):
35
+ node_input_ids_by_name: Dict[str, UUID] = {}
36
+ for key, value in inst.items():
37
+ node_input_ids_by_name.update(_get_node_input_ids_by_ref(f"{path}.{key}", value))
38
+ return node_input_ids_by_name
39
+
40
+ if isinstance(inst, BaseDescriptor):
41
+ return {path: uuid4_from_hash(f"{node_class.__id__}|{path}")}
42
+
43
+ return {}
44
+
45
+ def exec_body(ns: Dict):
46
+ output_display = {
47
+ ref: NodeOutputDisplay(id=node_class.__output_ids__[ref.name], name=ref.name)
48
+ for ref in node_class.Outputs
49
+ if ref.name in node_class.__output_ids__
50
+ }
51
+ if output_display:
52
+ ns["output_display"] = output_display
53
+
54
+ node_input_ids_by_name: Dict[str, UUID] = {}
55
+ for ref in node_class:
56
+ node_input_ids_by_name.update(_get_node_input_ids_by_ref(ref.name, ref.instance))
57
+
58
+ if node_input_ids_by_name:
59
+ ns["node_input_ids_by_name"] = node_input_ids_by_name
60
+
29
61
  NodeDisplayClass = types.new_class(
30
62
  f"{node_class.__name__}Display",
31
63
  bases=(NodeDisplayBaseClass,),
64
+ exec_body=exec_body,
32
65
  )
33
- output_display = {
34
- ref: NodeOutputDisplay(id=node_class.__output_ids__[ref.name], name=ref.name)
35
- for ref in node_class.Outputs
36
- if ref.name in node_class.__output_ids__
37
- }
38
- if output_display:
39
- setattr(NodeDisplayClass, "output_display", output_display)
40
66
 
41
67
  return NodeDisplayClass
@@ -1,9 +1,14 @@
1
+ import re
2
+ import types
1
3
  from uuid import UUID
2
- from typing import Any, Callable, Generic, Optional, TypeVar, cast
4
+ from typing import Any, Callable, Dict, Generic, Optional, Type, TypeVar, cast
3
5
 
6
+ from vellum.workflows.nodes.bases.base import BaseNode
4
7
  from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
5
8
  from vellum.workflows.nodes.utils import get_wrapped_node
6
9
  from vellum.workflows.types.core import JsonArray, JsonObject
10
+ from vellum.workflows.types.utils import get_original_base
11
+ from vellum.workflows.utils.uuids import uuid4_from_hash
7
12
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
8
13
  from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
9
14
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
@@ -37,3 +42,52 @@ class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Ge
37
42
  serialized_wrapped_node["adornments"] = adornments + [adornment] if adornment else adornments
38
43
 
39
44
  return serialized_wrapped_node
45
+
46
+ @classmethod
47
+ def wrap(cls, **kwargs: Any) -> Callable[..., Type[BaseNodeDisplay]]:
48
+ NodeDisplayType = TypeVar("NodeDisplayType", bound=BaseNodeDisplay)
49
+
50
+ def decorator(inner_cls: Type[NodeDisplayType]) -> Type[NodeDisplayType]:
51
+ node_class = inner_cls.infer_node_class()
52
+ wrapped_node_class = cast(Type[BaseNode], node_class.__wrapped_node__)
53
+
54
+ # `mypy` is wrong here, `cls` is indexable bc it's Generic
55
+ BaseAdornmentDisplay = cls[node_class] # type: ignore[index]
56
+
57
+ def exec_body(ns: Dict):
58
+ for key, kwarg in kwargs.items():
59
+ ns[key] = kwarg
60
+
61
+ if "node_id" not in kwargs:
62
+ ns["node_id"] = uuid4_from_hash(node_class.__qualname__)
63
+
64
+ AdornmentDisplay = types.new_class(
65
+ re.sub(r"^Base", "", cls.__name__), bases=(BaseAdornmentDisplay,), exec_body=exec_body
66
+ )
67
+
68
+ setattr(inner_cls, "__adorned_by__", AdornmentDisplay)
69
+
70
+ # We must edit the node display class to use __wrapped_node__ everywhere it
71
+ # references the adorned node class, which is three places:
72
+
73
+ # 1. The node display class' parameterized type
74
+ original_base_node_display = get_original_base(inner_cls)
75
+ original_base_node_display.__args__ = (wrapped_node_class,)
76
+ inner_cls._node_display_registry[wrapped_node_class] = inner_cls
77
+ inner_cls.__annotate_node__()
78
+
79
+ # 2. The node display class' output displays
80
+ old_outputs = list(inner_cls.output_display.keys())
81
+ for old_output in old_outputs:
82
+ new_output = getattr(wrapped_node_class.Outputs, old_output.name)
83
+ inner_cls.output_display[new_output] = inner_cls.output_display.pop(old_output)
84
+
85
+ # 3. The node display class' port displays
86
+ old_ports = list(inner_cls.port_displays.keys())
87
+ for old_port in old_ports:
88
+ new_port = getattr(wrapped_node_class.Ports, old_port.name)
89
+ inner_cls.port_displays[new_port] = inner_cls.port_displays.pop(old_port)
90
+
91
+ return inner_cls
92
+
93
+ return decorator
@@ -1,14 +1,12 @@
1
1
  import inspect
2
- from typing import Any, Callable, Generic, Optional, Tuple, Type, TypeVar, cast
2
+ from typing import Any, Generic, Tuple, Type, TypeVar, cast
3
3
 
4
4
  from vellum.workflows.descriptors.base import BaseDescriptor
5
- from vellum.workflows.errors.types import WorkflowErrorCode
6
5
  from vellum.workflows.nodes.bases.base import BaseNode
7
6
  from vellum.workflows.nodes.core.retry_node.node import RetryNode
8
7
  from vellum.workflows.nodes.utils import ADORNMENT_MODULE_NAME
9
8
  from vellum.workflows.references.output import OutputReference
10
9
  from vellum.workflows.types.core import JsonArray, JsonObject
11
- from vellum.workflows.types.utils import get_original_base
12
10
  from vellum.workflows.utils.uuids import uuid4_from_hash
13
11
  from vellum.workflows.workflows.base import BaseWorkflow
14
12
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
@@ -79,54 +77,3 @@ class BaseRetryNodeDisplay(BaseAdornmentNodeDisplay[_RetryNodeType], Generic[_Re
79
77
 
80
78
  inner_output = getattr(inner_node.Outputs, output.name)
81
79
  return node_display.get_node_output_display(inner_output)
82
-
83
- @classmethod
84
- def wrap(
85
- cls,
86
- max_attempts: int,
87
- delay: Optional[float] = None,
88
- retry_on_error_code: Optional[WorkflowErrorCode] = None,
89
- retry_on_condition: Optional[BaseDescriptor] = None,
90
- ) -> Callable[..., Type["BaseRetryNodeDisplay"]]:
91
- _max_attempts = max_attempts
92
- _delay = delay
93
- _retry_on_error_code = retry_on_error_code
94
- _retry_on_condition = retry_on_condition
95
-
96
- NodeDisplayType = TypeVar("NodeDisplayType", bound=BaseNodeDisplay)
97
-
98
- def decorator(inner_cls: Type[NodeDisplayType]) -> Type[NodeDisplayType]:
99
- node_class = inner_cls.infer_node_class()
100
- wrapped_node_class = cast(Type[BaseNode], node_class.__wrapped_node__)
101
-
102
- class RetryNodeDisplay(BaseRetryNodeDisplay[node_class]): # type: ignore[valid-type]
103
- max_attempts = _max_attempts
104
- delay = _delay
105
- retry_on_error_code = _retry_on_error_code
106
- retry_on_condition = _retry_on_condition
107
-
108
- setattr(inner_cls, "__adorned_by__", RetryNodeDisplay)
109
-
110
- # We must edit the node display class to use __wrapped_node__ everywhere it
111
- # references the adorned node class, which is three places:
112
-
113
- # 1. The node display class' parameterized type
114
- original_base_node_display = get_original_base(inner_cls)
115
- original_base_node_display.__args__ = (wrapped_node_class,)
116
- inner_cls._node_display_registry[wrapped_node_class] = inner_cls
117
-
118
- # 2. The node display class' output displays
119
- old_outputs = list(inner_cls.output_display.keys())
120
- for old_output in old_outputs:
121
- new_output = getattr(wrapped_node_class.Outputs, old_output.name)
122
- inner_cls.output_display[new_output] = inner_cls.output_display.pop(old_output)
123
-
124
- # 3. The node display class' port displays
125
- old_ports = list(inner_cls.port_displays.keys())
126
- for old_port in old_ports:
127
- new_port = getattr(wrapped_node_class.Ports, old_port.name)
128
- inner_cls.port_displays[new_port] = inner_cls.port_displays.pop(old_port)
129
-
130
- return inner_cls
131
-
132
- return decorator
@@ -23,14 +23,7 @@ class BaseTemplatingNodeDisplay(BaseNodeVellumDisplay[_TemplatingNodeType], Gene
23
23
  node = self._node
24
24
  node_id = self.node_id
25
25
 
26
- template_input_id = self.template_input_id or next(
27
- (
28
- UUID(input_id) if isinstance(input_id, str) else input_id
29
- for input_name, input_id in self.node_input_ids_by_name.items()
30
- if input_name == TEMPLATE_INPUT_NAME
31
- ),
32
- None,
33
- )
26
+ template_input_id = self.template_input_id or self.node_input_ids_by_name.get(TEMPLATE_INPUT_NAME)
34
27
 
35
28
  template_node_input = create_node_input(
36
29
  node_id=node_id,
@@ -1,6 +1,6 @@
1
1
  import inspect
2
2
  from uuid import UUID
3
- from typing import Any, Callable, ClassVar, Generic, Optional, Tuple, Type, TypeVar, cast
3
+ from typing import Any, ClassVar, Generic, Optional, Tuple, Type, TypeVar, cast
4
4
 
5
5
  from vellum.workflows.descriptors.base import BaseDescriptor
6
6
  from vellum.workflows.nodes.bases.base import BaseNode
@@ -8,7 +8,6 @@ from vellum.workflows.nodes.core.try_node.node import TryNode
8
8
  from vellum.workflows.nodes.utils import ADORNMENT_MODULE_NAME
9
9
  from vellum.workflows.references.output import OutputReference
10
10
  from vellum.workflows.types.core import JsonArray, JsonObject
11
- from vellum.workflows.types.utils import get_original_base
12
11
  from vellum.workflows.utils.uuids import uuid4_from_hash
13
12
  from vellum.workflows.workflows.base import BaseWorkflow
14
13
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
@@ -93,43 +92,3 @@ class BaseTryNodeDisplay(BaseAdornmentNodeDisplay[_TryNodeType], Generic[_TryNod
93
92
 
94
93
  inner_output = getattr(inner_node.Outputs, output.name)
95
94
  return node_display.get_node_output_display(inner_output)
96
-
97
- @classmethod
98
- def wrap(cls, error_output_id: Optional[UUID] = None) -> Callable[..., Type["BaseTryNodeDisplay"]]:
99
- _error_output_id = error_output_id
100
-
101
- NodeDisplayType = TypeVar("NodeDisplayType", bound=BaseNodeDisplay)
102
-
103
- def decorator(inner_cls: Type[NodeDisplayType]) -> Type[NodeDisplayType]:
104
- node_class = inner_cls.infer_node_class()
105
- wrapped_node_class = cast(Type[BaseNode], node_class.__wrapped_node__)
106
-
107
- # Mypy gets mad about dynamic parameter types like this, but it's fine
108
- class TryNodeDisplay(BaseTryNodeDisplay[node_class]): # type: ignore[valid-type]
109
- error_output_id = _error_output_id
110
-
111
- setattr(inner_cls, "__adorned_by__", TryNodeDisplay)
112
-
113
- # We must edit the node display class to use __wrapped_node__ everywhere it
114
- # references the adorned node class, which is three places:
115
-
116
- # 1. The node display class' parameterized type
117
- original_base_node_display = get_original_base(inner_cls)
118
- original_base_node_display.__args__ = (wrapped_node_class,)
119
- inner_cls._node_display_registry[wrapped_node_class] = inner_cls
120
-
121
- # 2. The node display class' output displays
122
- old_outputs = list(inner_cls.output_display.keys())
123
- for old_output in old_outputs:
124
- new_output = getattr(wrapped_node_class.Outputs, old_output.name)
125
- inner_cls.output_display[new_output] = inner_cls.output_display.pop(old_output)
126
-
127
- # 3. The node display class' port displays
128
- old_ports = list(inner_cls.port_displays.keys())
129
- for old_port in old_ports:
130
- new_port = getattr(wrapped_node_class.Ports, old_port.name)
131
- inner_cls.port_displays[new_port] = inner_cls.port_displays.pop(old_port)
132
-
133
- return inner_cls
134
-
135
- return decorator
@@ -1,4 +1,3 @@
1
- import pytest
2
1
  from uuid import UUID
3
2
  from typing import Dict
4
3
 
@@ -231,11 +230,7 @@ def test_vellum_workflow_display__serialize_with_unused_nodes_and_edges():
231
230
  assert edge_found, "Edge between unused nodes NodeB and NodeC not found in serialized output"
232
231
 
233
232
 
234
- def test_parse_json_not_supported_in_ui():
235
- """
236
- Test that verifies ParseJsonExpression is not yet supported in the UI.
237
- This test should fail once UI support is added, at which point it should be updated.
238
- """
233
+ def test_vellum_workflow_display__serialize_with_parse_json_expression():
239
234
  # GIVEN a workflow that uses the parse_json function
240
235
  from vellum.workflows.references.constant import ConstantValueReference
241
236
 
@@ -249,13 +244,55 @@ def test_parse_json_not_supported_in_ui():
249
244
  class Outputs(BaseWorkflow.Outputs):
250
245
  final = JsonNode.Outputs.json_result
251
246
 
252
- # WHEN we attempt to serialize it
247
+ # AND a display class for this workflow
253
248
  workflow_display = get_workflow_display(
254
249
  base_display_class=VellumWorkflowDisplay,
255
250
  workflow_class=Workflow,
256
251
  )
257
252
 
258
- with pytest.raises(ValueError) as exc_info:
259
- workflow_display.serialize()
253
+ # WHEN we serialize the workflow
254
+ exec_config = workflow_display.serialize()
255
+
256
+ # THEN the serialized workflow contains the parse_json expression
257
+ raw_data = exec_config["workflow_raw_data"]
258
+ assert isinstance(raw_data, dict)
260
259
 
261
- assert "ParseJsonExpression is not supported in the UI" == str(exc_info.value)
260
+ nodes = raw_data["nodes"]
261
+ assert isinstance(nodes, list)
262
+
263
+ json_node = None
264
+ for node in nodes:
265
+ assert isinstance(node, dict)
266
+ definition = node.get("definition")
267
+ if node.get("type") == "GENERIC" and isinstance(definition, dict) and definition.get("name") == "JsonNode":
268
+ json_node = node
269
+ break
270
+
271
+ assert json_node is not None
272
+
273
+ outputs = json_node.get("outputs", [])
274
+ assert isinstance(outputs, list)
275
+
276
+ json_result = None
277
+ for output in outputs:
278
+ assert isinstance(output, dict)
279
+ if output.get("name") == "json_result":
280
+ json_result = output
281
+ break
282
+
283
+ assert json_result == {
284
+ "id": "44c7d94c-a76a-4151-9b95-85a31764f18f",
285
+ "name": "json_result",
286
+ "type": "JSON",
287
+ "value": {
288
+ "type": "UNARY_EXPRESSION",
289
+ "lhs": {
290
+ "type": "CONSTANT_VALUE",
291
+ "value": {
292
+ "type": "STRING",
293
+ "value": '{"key": "value"}',
294
+ },
295
+ },
296
+ "operator": "parseJson",
297
+ },
298
+ }
@@ -1003,3 +1003,70 @@ def test_serialize_node__or_then_and(serialize_node):
1003
1003
  serialized_node,
1004
1004
  ignore_order=True,
1005
1005
  )
1006
+
1007
+
1008
+ class ParseJsonGenericNode(BaseNode):
1009
+ class Ports(BaseNode.Ports):
1010
+ if_branch = Port.on_if(Inputs.input.parse_json().equals({"key": "value"}))
1011
+
1012
+
1013
+ def test_serialize_node__parse_json(serialize_node):
1014
+ input_id = uuid4()
1015
+ serialized_node = serialize_node(
1016
+ node_class=ParseJsonGenericNode,
1017
+ global_workflow_input_displays={Inputs.input: WorkflowInputsDisplay(id=input_id)},
1018
+ )
1019
+
1020
+ assert not DeepDiff(
1021
+ {
1022
+ "id": "60edd9b2-9bad-470f-832c-c5f1aa9a253e",
1023
+ "label": "ParseJsonGenericNode",
1024
+ "type": "GENERIC",
1025
+ "display_data": {"position": {"x": 0.0, "y": 0.0}},
1026
+ "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
1027
+ "definition": {
1028
+ "name": "ParseJsonGenericNode",
1029
+ "module": [
1030
+ "vellum_ee",
1031
+ "workflows",
1032
+ "display",
1033
+ "tests",
1034
+ "workflow_serialization",
1035
+ "generic_nodes",
1036
+ "test_ports_serialization",
1037
+ ],
1038
+ },
1039
+ "trigger": {"id": "0ccc19cb-174b-440b-8d08-6c84c571fb8f", "merge_behavior": "AWAIT_ATTRIBUTES"},
1040
+ "ports": [
1041
+ {
1042
+ "id": "abe1e6db-14df-4d3c-b059-d17933ab8c02",
1043
+ "type": "IF",
1044
+ "name": "if_branch",
1045
+ "expression": {
1046
+ "type": "BINARY_EXPRESSION",
1047
+ "lhs": {
1048
+ "type": "UNARY_EXPRESSION",
1049
+ "lhs": {
1050
+ "type": "WORKFLOW_INPUT",
1051
+ "input_variable_id": str(input_id),
1052
+ },
1053
+ "operator": "parseJson",
1054
+ },
1055
+ "operator": "=",
1056
+ "rhs": {
1057
+ "type": "CONSTANT_VALUE",
1058
+ "value": {
1059
+ "type": "JSON",
1060
+ "value": {"key": "value"},
1061
+ },
1062
+ },
1063
+ },
1064
+ }
1065
+ ],
1066
+ "adornments": None,
1067
+ "attributes": [],
1068
+ "outputs": [],
1069
+ },
1070
+ serialized_node,
1071
+ ignore_order=True,
1072
+ )
@@ -28,6 +28,7 @@ from vellum.workflows.expressions.less_than_or_equal_to import LessThanOrEqualTo
28
28
  from vellum.workflows.expressions.not_between import NotBetweenExpression
29
29
  from vellum.workflows.expressions.not_in import NotInExpression
30
30
  from vellum.workflows.expressions.or_ import OrExpression
31
+ from vellum.workflows.expressions.parse_json import ParseJsonExpression
31
32
  from vellum.workflows.nodes.bases.base import BaseNode
32
33
  from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value
33
34
  from vellum.workflows.references import OutputReference, WorkflowInputReference
@@ -163,5 +164,7 @@ def convert_descriptor_to_operator(descriptor: BaseDescriptor) -> LogicalOperato
163
164
  return "or"
164
165
  elif isinstance(descriptor, CoalesceExpression):
165
166
  return "coalesce"
167
+ elif isinstance(descriptor, ParseJsonExpression):
168
+ return "parseJson"
166
169
  else:
167
170
  raise ValueError(f"Unsupported descriptor type: {descriptor}")
@@ -212,8 +212,6 @@ class BaseWorkflowDisplay(
212
212
 
213
213
  port_displays: Dict[Port, PortDisplay] = {}
214
214
 
215
- # TODO: We should still serialize nodes that are in the workflow's directory but aren't used in the graph.
216
- # https://app.shortcut.com/vellum/story/5394
217
215
  for node in self._workflow.get_nodes():
218
216
  extracted_node_displays = self._extract_node_displays(node)
219
217
 
@@ -403,11 +401,11 @@ class BaseWorkflowDisplay(
403
401
  for input in display_context.workflow_input_displays
404
402
  }
405
403
  node_displays = {
406
- str(node.__id__): display_context.node_displays[node] for node in display_context.node_displays
404
+ node.__id__: (node, display_context.node_displays[node]) for node in display_context.node_displays
407
405
  }
408
406
  node_event_displays = {}
409
407
  for node_id in node_displays:
410
- current_node_display = node_displays[node_id]
408
+ node, current_node_display = node_displays[node_id]
411
409
  input_display = {}
412
410
  if isinstance(current_node_display, BaseNodeVellumDisplay):
413
411
  input_display = current_node_display.node_input_ids_by_name
@@ -418,7 +416,6 @@ class BaseWorkflowDisplay(
418
416
  port_display_meta = {
419
417
  port.name: current_node_display.port_displays[port].id for port in current_node_display.port_displays
420
418
  }
421
- node = current_node_display._node
422
419
  subworkflow_display_context: Optional[WorkflowEventDisplayContext] = None
423
420
  if hasattr(node, "subworkflow"):
424
421
  # All nodes that have a subworkflow attribute are currently expected to call them `subworkflow`
@@ -432,7 +429,7 @@ class BaseWorkflowDisplay(
432
429
  )
433
430
  subworkflow_display_context = subworkflow_display.get_event_display_context()
434
431
 
435
- node_event_displays[node_id] = NodeEventDisplayContext(
432
+ node_event_displays[str(node_id)] = NodeEventDisplayContext(
436
433
  input_display=input_display,
437
434
  output_display=output_display,
438
435
  port_display=port_display_meta,
@@ -1,9 +1,13 @@
1
1
  import pytest
2
+ from uuid import uuid4
2
3
 
3
4
  from vellum.workflows.nodes.bases.base import BaseNode
4
5
  from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
6
+ from vellum.workflows.nodes.core.retry_node.node import RetryNode
7
+ from vellum.workflows.nodes.core.templating_node.node import TemplatingNode
5
8
  from vellum.workflows.workflows.base import BaseWorkflow
6
9
  from vellum_ee.workflows.display.nodes import BaseNodeDisplay
10
+ from vellum_ee.workflows.display.nodes.vellum.retry_node import BaseRetryNodeDisplay
7
11
  from vellum_ee.workflows.display.vellum import NodeDisplayData, NodeDisplayPosition
8
12
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
9
13
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
@@ -139,3 +143,51 @@ def test_get_event_display_context__node_display_to_include_subworkflow_display(
139
143
 
140
144
  assert node_event_display.subworkflow_display is not None
141
145
  assert str(InnerNode.__id__) in node_event_display.subworkflow_display.node_displays
146
+
147
+
148
+ def test_get_event_display_context__node_display_for_adornment_nodes():
149
+ # GIVEN a simple workflow with a retry node adornment
150
+ @RetryNode.wrap(max_attempts=4)
151
+ class MyNode(BaseNode):
152
+ pass
153
+
154
+ class MyWorkflow(BaseWorkflow):
155
+ graph = MyNode
156
+
157
+ # AND a display class for the node
158
+ inner_node_id = uuid4()
159
+
160
+ @BaseRetryNodeDisplay.wrap()
161
+ class MyNodeDisplay(BaseNodeDisplay[MyNode]):
162
+ node_id = inner_node_id
163
+
164
+ # WHEN we gather the event display context
165
+ display_context = VellumWorkflowDisplay(MyWorkflow).get_event_display_context()
166
+
167
+ # THEN the subworkflow display should be included
168
+ assert str(MyNode.__id__) in display_context.node_displays
169
+ node_event_display = display_context.node_displays[str(MyNode.__id__)]
170
+ assert node_event_display.subworkflow_display is not None
171
+ assert str(inner_node_id) in node_event_display.subworkflow_display.node_displays
172
+
173
+
174
+ def test_get_event_display_context__templating_node_input_display():
175
+ # GIVEN a simple workflow with a templating node referencing another node output
176
+ class DataNode(BaseNode):
177
+ class Outputs:
178
+ bar: str
179
+
180
+ class MyNode(TemplatingNode):
181
+ inputs = {"foo": DataNode.Outputs.bar}
182
+
183
+ class MyWorkflow(BaseWorkflow):
184
+ graph = DataNode >> MyNode
185
+
186
+ # WHEN we gather the event display context
187
+ display_context = VellumWorkflowDisplay(MyWorkflow).get_event_display_context()
188
+
189
+ # THEN the subworkflow display should be included
190
+ assert str(MyNode.__id__) in display_context.node_displays
191
+ node_event_display = display_context.node_displays[str(MyNode.__id__)]
192
+
193
+ assert node_event_display.input_display.keys() == {"inputs.foo"}