vellum-ai 0.14.20__py3-none-any.whl → 0.14.22__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 (25) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/utils/templating/constants.py +7 -2
  3. vellum/utils/templating/custom_filters.py +9 -0
  4. vellum/workflows/README.md +3 -2
  5. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +18 -0
  6. vellum/workflows/nodes/displayable/api_node/node.py +1 -1
  7. vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +4 -2
  8. vellum/workflows/nodes/displayable/bases/api_node/node.py +8 -3
  9. vellum/workflows/nodes/tests/test_utils.py +34 -2
  10. vellum/workflows/nodes/utils.py +26 -0
  11. {vellum_ai-0.14.20.dist-info → vellum_ai-0.14.22.dist-info}/METADATA +2 -3
  12. {vellum_ai-0.14.20.dist-info → vellum_ai-0.14.22.dist-info}/RECORD +25 -25
  13. vellum_ee/workflows/display/base.py +0 -4
  14. vellum_ee/workflows/display/nodes/vellum/api_node.py +9 -16
  15. vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +63 -42
  16. vellum_ee/workflows/display/nodes/vellum/merge_node.py +8 -5
  17. vellum_ee/workflows/display/types.py +2 -2
  18. vellum_ee/workflows/display/vellum.py +1 -1
  19. vellum_ee/workflows/display/workflows/base_workflow_display.py +12 -41
  20. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +40 -0
  21. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +2 -4
  22. vellum_ee/workflows/tests/local_workflow/display/workflow.py +3 -5
  23. {vellum_ai-0.14.20.dist-info → vellum_ai-0.14.22.dist-info}/LICENSE +0 -0
  24. {vellum_ai-0.14.20.dist-info → vellum_ai-0.14.22.dist-info}/WHEEL +0 -0
  25. {vellum_ai-0.14.20.dist-info → vellum_ai-0.14.22.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.20",
21
+ "X-Fern-SDK-Version": "0.14.22",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -10,7 +10,7 @@ import pydash
10
10
  import pytz
11
11
  import yaml
12
12
 
13
- from vellum.utils.templating.custom_filters import is_valid_json_string, replace
13
+ from vellum.utils.templating.custom_filters import is_valid_json_string, replace, safe_tojson
14
14
 
15
15
  DEFAULT_JINJA_GLOBALS: Dict[str, Any] = {
16
16
  "datetime": datetime,
@@ -24,9 +24,14 @@ DEFAULT_JINJA_GLOBALS: Dict[str, Any] = {
24
24
  "yaml": yaml,
25
25
  }
26
26
 
27
- FilterFunc = Union[Callable[[Union[str, bytes]], bool], Callable[[Any, Any, Any], str]]
27
+ FilterFunc = Union[
28
+ Callable[[Union[str, bytes]], bool], # is_valid_json_string
29
+ Callable[[Any, Any, Any], str], # replace
30
+ Callable[[Any], str], # safe_tojson
31
+ ]
28
32
 
29
33
  DEFAULT_JINJA_CUSTOM_FILTERS: Dict[str, FilterFunc] = {
30
34
  "is_valid_json_string": is_valid_json_string,
31
35
  "replace": replace,
36
+ "tojson": safe_tojson,
32
37
  }
@@ -1,6 +1,9 @@
1
1
  import json
2
2
  from typing import Any, Union
3
3
 
4
+ from jinja2 import Undefined
5
+ from jinja2.utils import htmlsafe_json_dumps
6
+
4
7
  from vellum.workflows.state.encoder import DefaultStateEncoder
5
8
 
6
9
 
@@ -31,3 +34,9 @@ def replace(s: Any, old: Any, new: Any) -> str:
31
34
  old_str = encode_to_str(old)
32
35
  new_str = encode_to_str(new)
33
36
  return s_str.replace(old_str, new_str)
37
+
38
+
39
+ def safe_tojson(value: Any) -> str:
40
+ if isinstance(value, Undefined):
41
+ return ""
42
+ return htmlsafe_json_dumps(value, cls=DefaultStateEncoder)
@@ -77,7 +77,8 @@ code to UI and vice versa.
77
77
  ```bash
78
78
  python my_workflow.py
79
79
 
80
- Note: To use most out-of-box Nodes, and to push/pull to/from the Velłum UI, you'll need a Vellum account and API key. [Talk to us](https://www.vellum.ai/landing-pages/request-demo) or visit our [pricing page](https://www.vellum.ai/pricing) for more information.
80
+ Note: To use most out-of-box Nodes, and to push/pull to/from the Vellum UI, you'll need a Vellum account and API key.
81
+ You can [sign up for free here](https://app.vellum.ai/signup?f=wsdk&utm_source=github&utm_medium=repo_quickstart&utm_campaign=sdk).
81
82
 
82
83
 
83
84
  ## Documentation
@@ -86,5 +87,5 @@ Complete documentation for the Vellum Workflows SDK can be found at https://docs
86
87
 
87
88
  ## Stability
88
89
 
89
- This SDK is currently in <Availability type="beta" /> and is subject to change. If you'd like to pariticpate in
90
+ This SDK is currently in <Availability type="beta" /> and is subject to change. If you'd like to participate in
90
91
  our beta program, please [contact us](https://docs.vellum.ai/home/getting-started/support).
@@ -299,3 +299,21 @@ def test_templating_node__empty_string_to_list():
299
299
 
300
300
  # THEN the output should be an empty list, not raise an exception
301
301
  assert outputs.result == []
302
+
303
+
304
+ def test_api_error_templating_node():
305
+ class UndefinedTemplatingNode(TemplatingNode[BaseState, str]):
306
+ template = """{{ foo | tojson }}"""
307
+ inputs = {
308
+ "bar": "bar",
309
+ # foo is not define
310
+ }
311
+
312
+ # GIVEN a templating node with an undefined value
313
+ node = UndefinedTemplatingNode()
314
+
315
+ # WHEN the node is run
316
+ outputs = node.run()
317
+
318
+ # THEN the output should be empty string
319
+ assert outputs.result == ""
@@ -50,7 +50,7 @@ class APINode(BaseAPINode):
50
50
  return self._run(
51
51
  method=self.method,
52
52
  url=self.url,
53
- data=self.data or self.json,
53
+ data=self.data,
54
54
  json=self.json,
55
55
  headers={**headers, **header_overrides},
56
56
  bearer_token=bearer_token,
@@ -20,7 +20,7 @@ def test_run_workflow__secrets(vellum_client):
20
20
  method = APIRequestMethod.POST
21
21
  authorization_type = AuthorizationType.BEARER_TOKEN
22
22
  url = "https://api.vellum.ai"
23
- body = {
23
+ json = {
24
24
  "key": "value",
25
25
  }
26
26
  headers = {
@@ -32,6 +32,7 @@ def test_run_workflow__secrets(vellum_client):
32
32
  terminal = node.run()
33
33
 
34
34
  assert vellum_client.execute_api.call_count == 1
35
+ assert vellum_client.execute_api.call_args.kwargs["body"] == {"key": "value"}
35
36
  bearer_token = vellum_client.execute_api.call_args.kwargs["bearer_token"]
36
37
  assert bearer_token == ClientVellumSecret(name="secret")
37
38
  assert terminal.headers == {"X-Response-Header": "bar"}
@@ -45,7 +46,7 @@ def test_api_node_raises_error_when_api_call_fails(vellum_client):
45
46
  method = APIRequestMethod.GET
46
47
  authorization_type = AuthorizationType.BEARER_TOKEN
47
48
  url = "https://api.vellum.ai"
48
- body = {
49
+ json = {
49
50
  "key": "value",
50
51
  }
51
52
  headers = {
@@ -64,6 +65,7 @@ def test_api_node_raises_error_when_api_call_fails(vellum_client):
64
65
 
65
66
  # AND the API call should have been made
66
67
  assert vellum_client.execute_api.call_count == 1
68
+ assert vellum_client.execute_api.call_args.kwargs["body"] == {"key": "value"}
67
69
 
68
70
 
69
71
  def test_api_node_defaults_to_get_method(vellum_client):
@@ -28,7 +28,7 @@ class BaseAPINode(BaseNode, Generic[StateType]):
28
28
  class Trigger(BaseNode.Trigger):
29
29
  merge_behavior = MergeBehavior.AWAIT_ANY
30
30
 
31
- url: str
31
+ url: str = ""
32
32
  method: Optional[APIRequestMethod] = APIRequestMethod.GET
33
33
  data: Optional[str] = None
34
34
  json: Optional[Json] = None
@@ -57,13 +57,18 @@ class BaseAPINode(BaseNode, Generic[StateType]):
57
57
  if isinstance(headers[header], VellumSecret):
58
58
  vellum_instance = True
59
59
  if vellum_instance or bearer_token:
60
- return self._vellum_execute_api(bearer_token, data, headers, method, url)
60
+ return self._vellum_execute_api(bearer_token, json, headers, method, url)
61
61
  else:
62
62
  return self._local_execute_api(data, headers, json, method, url)
63
63
 
64
64
  def _local_execute_api(self, data, headers, json, method, url):
65
65
  try:
66
- prepped = Request(method=method.value, url=url, data=data, json=json, headers=headers).prepare()
66
+ if data is not None:
67
+ prepped = Request(method=method.value, url=url, data=data, headers=headers).prepare()
68
+ elif json is not None:
69
+ prepped = Request(method=method.value, url=url, json=json, headers=headers).prepare()
70
+ else:
71
+ prepped = Request(method=method.value, url=url, headers=headers).prepare()
67
72
  except Exception as e:
68
73
  raise NodeException(f"Failed to prepare HTTP request: {e}", code=WorkflowErrorCode.PROVIDER_ERROR)
69
74
  try:
@@ -1,11 +1,14 @@
1
1
  import pytest
2
- from typing import List, Union
2
+ from typing import Any, List, Union
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
6
+ from vellum.client.types.chat_message import ChatMessage
7
+ from vellum.client.types.function_call import FunctionCall as FunctionCallType
8
+ from vellum.client.types.vellum_value import VellumValue
6
9
  from vellum.workflows.errors.types import WorkflowErrorCode
7
10
  from vellum.workflows.exceptions import NodeException
8
- from vellum.workflows.nodes.utils import parse_type_from_str
11
+ from vellum.workflows.nodes.utils import cast_to_output_type, parse_type_from_str
9
12
  from vellum.workflows.types.core import Json
10
13
 
11
14
 
@@ -131,3 +134,32 @@ def test_parse_type_from_str_error_cases(input_str, output_type, expected_except
131
134
  assert excinfo.value.code == expected_code
132
135
 
133
136
  assert expected_message in str(excinfo.value)
137
+
138
+
139
+ @pytest.mark.parametrize(
140
+ "output_type,expected_result",
141
+ [
142
+ (str, ""), # String
143
+ (int, 0), # Number
144
+ (float, 0.0), # Number
145
+ (Any, None), # Json
146
+ (FunctionCallType, {}), # FunctionCall
147
+ (List[ChatMessage], []), # Chat History
148
+ (List[VellumValue], []), # Array
149
+ (Union[float, int], 0.0), # Union
150
+ ],
151
+ ids=[
152
+ "string",
153
+ "integer",
154
+ "float",
155
+ "json",
156
+ "function_call",
157
+ "chat_history",
158
+ "array",
159
+ "union",
160
+ ],
161
+ )
162
+ def test_cast_to_output_type_none_value(output_type, expected_result):
163
+ """Test that cast_to_output_type returns appropriate default values when None is provided."""
164
+ result = cast_to_output_type(None, output_type)
165
+ assert result == expected_result
@@ -6,6 +6,7 @@ from typing import Any, Callable, Dict, ForwardRef, List, Optional, Type, TypeVa
6
6
 
7
7
  from pydantic import BaseModel, create_model
8
8
 
9
+ from vellum.client.types.function_call import FunctionCall
9
10
  from vellum.workflows.errors.types import WorkflowErrorCode
10
11
  from vellum.workflows.exceptions import NodeException
11
12
  from vellum.workflows.nodes import BaseNode
@@ -195,7 +196,32 @@ def _clean_output_type(output_type: Any) -> Any:
195
196
  return output_type
196
197
 
197
198
 
199
+ def _get_default_value(output_type: Any) -> Any:
200
+ origin = get_origin(output_type)
201
+ args = get_args(output_type)
202
+
203
+ if output_type is int or output_type is float:
204
+ return 0.0
205
+ elif output_type is str:
206
+ return ""
207
+ elif origin is list:
208
+ return []
209
+ elif output_type is FunctionCall:
210
+ return {}
211
+ elif origin is Union:
212
+ # Always use the first argument type's default value
213
+ if args:
214
+ return _get_default_value(args[0])
215
+ return None
216
+ else:
217
+ # We defined Json as Any now which is why it returns None
218
+ return None
219
+
220
+
198
221
  def cast_to_output_type(result: Any, output_type: Any) -> Any:
222
+ if result is None:
223
+ return _get_default_value(output_type)
224
+
199
225
  clean_output_type = _clean_output_type(output_type)
200
226
  DynamicModel = create_model("Output", output_type=(clean_output_type, ...))
201
227
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.20
3
+ Version: 0.14.22
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -90,8 +90,7 @@ Description-Content-Type: text/markdown
90
90
 
91
91
  ## Get Started
92
92
 
93
- Most functionality within the SDK requires a Vellum account and API key. To sign up, [talk to us](https://www.vellum.ai/landing-pages/request-demo)
94
- or visit our [pricing page](https://www.vellum.ai/pricing).
93
+ Most functionality within the Vellum SDK requires a Vellum account and API key. You can sign up for free [here](https://app.vellum.ai/signup?f=wsdk&utm_source=github&utm_medium=repo_readme&utm_campaign=sdk) or visit our [pricing page](https://www.vellum.ai/pricing) for paid options.
95
94
 
96
95
  Even without a Vellum account, you can use the Workflows SDK to define the control flow of your AI systems. [Learn
97
96
  more below](#workflows-sdk).
@@ -22,7 +22,7 @@ vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  vellum_ee/workflows/display/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- vellum_ee/workflows/display/base.py,sha256=-IR5QuWaZKAQHC-8L7T7FLqOYsjzz-tgmKrXOO7OKhY,2040
25
+ vellum_ee/workflows/display/base.py,sha256=xhrMfirVLBuIeDuxdOUmtJ6CcfCx8DChFo-4HfMY6uc,1883
26
26
  vellum_ee/workflows/display/nodes/__init__.py,sha256=436iSAh_Ex5tC68oEYvNgPu05ZVIAVXnS4PKGrQeZ0Y,321
27
27
  vellum_ee/workflows/display/nodes/base_node_display.py,sha256=4xIkrKXVryaD0fPd7RxSpzIqPg6Y14NEB7j7m0DrJvM,18974
28
28
  vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=rhbWYBQSXkpEPIvTa--LPwhsl8RnX4KyU6Ax0M0A0pU,1701
@@ -32,8 +32,8 @@ vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=QqR3Ly0
32
32
  vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6mLhstQAvEACbGk,247
33
33
  vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
34
34
  vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nUIgH2s0-7IbQRNrBhLPyRNe8YIrx3Yo9HeeW-aXXFk,1668
35
- vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=hoV-cUtS6H9kmRQXHd2py95GRWI_dAnnaPwvlNBkDOQ,8571
36
- vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py,sha256=oJAQrAm5iFQq0_fX94sMbS3RQEK1M1VsoUck4vsPs9A,5820
35
+ vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=lumtypr0JEZfiA32hcs_olTmnT0wIUzPBy0pnZVfyU4,8532
36
+ vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py,sha256=Bzqplrnx-bDIRD1JgU7036m8pSWSO45zEReNv8RJTu4,6379
37
37
  vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=IYx0nll0t-tsPcjfgpfHMZE4FJgMFIsOiaQanGLYF0Q,4410
38
38
  vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=ybLIa4uclqVIy3VAQvI1ivg2tnK5Ug_1R5a69DFqL7E,11104
39
39
  vellum_ee/workflows/display/nodes/vellum/error_node.py,sha256=I1Jkp2htRINJATtv1e-zs9BrReFX842djpiVgBPHDYg,2186
@@ -42,7 +42,7 @@ vellum_ee/workflows/display/nodes/vellum/guardrail_node.py,sha256=aYZSJTxknU4LMi
42
42
  vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py,sha256=3fJzrFoGJX_igzWKgXp7f7-owdVBG6xuy-kBSWNbNxc,8734
43
43
  vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py,sha256=MU9I8CB1X1TgL1aa1eT6DHWwNJ-2v79t74xl0oy-fBo,5510
44
44
  vellum_ee/workflows/display/nodes/vellum/map_node.py,sha256=8CPnn06HIBxBOiECevUffeVmQmCpec6WtPQnNl9gj9Y,3748
45
- vellum_ee/workflows/display/nodes/vellum/merge_node.py,sha256=HkNMgdQELiON42jdO-xDLmqrEKdGx1RVqrz2DXNTLS8,3239
45
+ vellum_ee/workflows/display/nodes/vellum/merge_node.py,sha256=xtyecs9mJ_WEwVpP12jxYwvehLXynhqLrPJ-Ahdk2GA,3232
46
46
  vellum_ee/workflows/display/nodes/vellum/note_node.py,sha256=TMb8txILu2uWjzoxaghjgjlzeBAgzn4vkP_8zSh2qoE,1151
47
47
  vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py,sha256=9YR6MtxVq8Bavb0ud2lZX0t2Y_NUd-dY9kRgq8WBz-Y,3093
48
48
  vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=LgokATi7sSS38Fil-XjqoR4t7AMOJ-GzXRw6p606Svo,3397
@@ -87,16 +87,16 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_n
87
87
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py,sha256=NdhE3lm7RMQ8DqkraPSq24IbOxNla9unbs4tsMWRzm4,3781
88
88
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=eD5686C9nWC5s6t08vbAnm9qf9t53gYQM-E1FwAa75c,3035
89
89
  vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py,sha256=huKAOeMJ2MKmp6XtbvMJTUadqynoV40Ypoz9jsBEBEQ,7431
90
- vellum_ee/workflows/display/types.py,sha256=NeaISY7bzq6yeBSeG8XblkOIKakm2dS-modocGGMnkc,2526
90
+ vellum_ee/workflows/display/types.py,sha256=UZ23Hv2ULWFrMaG1mRSbVnPEa9YvuLlv0dOMkA2j5_Y,2466
91
91
  vellum_ee/workflows/display/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
92
  vellum_ee/workflows/display/utils/expressions.py,sha256=9FpOslDI-RCR5m4TgAu9KCHh4aTVnh7CHR2ykyMUDw0,1151
93
93
  vellum_ee/workflows/display/utils/vellum.py,sha256=EVPQUSsZ3OIeLTEbV6LHPor37t9fnj9kJxDqP4PmTLQ,8234
94
- vellum_ee/workflows/display/vellum.py,sha256=JBdPRAaHxn-_NCsCLZbeWoiquhsj5DcAYgoUxdSHpzg,5168
94
+ vellum_ee/workflows/display/vellum.py,sha256=bevbLCd2KqJBKqJ3lQayeRfjY7x1Djf57F9iJ-6KBJw,5162
95
95
  vellum_ee/workflows/display/workflows/__init__.py,sha256=kapXsC67VJcgSuiBMa86FdePG5A9kMB5Pi4Uy1O2ob4,207
96
- vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=9IRGsoTJN4T1aLuVkfF6IW7gXYKesW32nT_jTgOhZvw,21175
96
+ vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=cvpDj5gDzoO1Sdt8BCo1acvZsNb30bpX-xGftl3u0xE,19940
97
97
  vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=kp0u8LN_2IwshLrhMImhpZx1hRyAcD5gXY-kDuuaGMQ,1269
98
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=Ny9VWjCxc1_w8Z5xiQEJmNmEwtjlDlxgFCrmCWSmFmA,8511
99
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=kr3fpegZOE4eAR6w543hpsv8GbYdMiSa1edku-PgnfI,16910
98
+ vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=TwxfmIpCL1xrJOjA54d52q5Ko0CFUT2bW60DVD0wrlY,10095
99
+ vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=AS-vMrM93KEjb02-ye0Il29l3bX74o0Q8P2Nvf9NIZ0,16683
100
100
  vellum_ee/workflows/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
101
  vellum_ee/workflows/server/virtual_file_loader.py,sha256=ET-Q83W5Cgqzqz3qtFNwtS2nJEIcm3VtvR5kffsT3VY,2262
102
102
  vellum_ee/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -111,7 +111,7 @@ vellum_ee/workflows/tests/local_workflow/display/__init__.py,sha256=xo75Uqb4aErO
111
111
  vellum_ee/workflows/tests/local_workflow/display/nodes/__init__.py,sha256=szW_mgOUriyZ6v1vlnevBgkzNi8g83-ihS98UOLHVcE,155
112
112
  vellum_ee/workflows/tests/local_workflow/display/nodes/final_output.py,sha256=Kv92TCREiZsB9531KZYaBIq83uHn7e_ECw_yAbD1qfk,1017
113
113
  vellum_ee/workflows/tests/local_workflow/display/nodes/templating_node.py,sha256=5cankEe1rDZlXKgILFSPbmN0tUZhIdmcFgz_AguXTJc,1229
114
- vellum_ee/workflows/tests/local_workflow/display/workflow.py,sha256=QV-TyH6FeqOZ53U8kj3m_annpYgRynG_hfrOuoV1cmk,2051
114
+ vellum_ee/workflows/tests/local_workflow/display/workflow.py,sha256=rYjUO0TN6GPCDldD4liWFPOLquJmw-sP4HYtyGspe08,2024
115
115
  vellum_ee/workflows/tests/local_workflow/inputs.py,sha256=4cgsZBoUbIY0Rs8gknC9yqxQ-sSoULclx_SAm1FT2RA,96
116
116
  vellum_ee/workflows/tests/local_workflow/metadata.json,sha256=rdu3h5qkFZiqhCAMxoyoWyMI0O8QALC5-URvSIW6F00,24
117
117
  vellum_ee/workflows/tests/local_workflow/nodes/__init__.py,sha256=1F6jxUpSKfPXPj4ZZKSbnX6Mg-VwF3euLJSZfGn6xkM,127
@@ -126,7 +126,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
126
126
  vellum/client/__init__.py,sha256=tKtdM1_GqmGq1gpi9ydWD_T-MM7fPn8QdHh8ww19cNI,117564
127
127
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
128
128
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
129
- vellum/client/core/client_wrapper.py,sha256=pleLWhyzDudDyYZo53X7C5M9jfmgGjkQ4KateNHPl7U,1869
129
+ vellum/client/core/client_wrapper.py,sha256=DpiUwVloITIZTAtfFwbXdgMJxWZ-qVWlr-Qx6Kc1pZc,1869
130
130
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
131
131
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
132
132
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -1292,8 +1292,8 @@ vellum/types/workspace_read.py,sha256=9CvgvK8Li8vL6qC5KX7f3-nEHslJ4lw2w07bvXcrjA
1292
1292
  vellum/types/workspace_secret_read.py,sha256=Z6QNXHxVHRdrLXSI31KxngePRwJTVoJYMXVbtPQwrxs,159
1293
1293
  vellum/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1294
1294
  vellum/utils/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1295
- vellum/utils/templating/constants.py,sha256=8OHMO6WFAEimbIaiHc5gy6s91D7_KvW-vTlEMWwvl_M,711
1296
- vellum/utils/templating/custom_filters.py,sha256=XVHriwazejRZmxB_eg4xHgCxl7AiQQ2sx-hRLMmylfU,885
1295
+ vellum/utils/templating/constants.py,sha256=mvAcJloHe1D9-LzM_jpzVZEJVYy326OCUMtSqD_vmo0,838
1296
+ vellum/utils/templating/custom_filters.py,sha256=qdbDSBPVIz-jKbetD4OnZ4In6O-SdeV0oZsixYoiWOY,1116
1297
1297
  vellum/utils/templating/exceptions.py,sha256=cDp140PP4OnInW4qAvg3KqiSiF70C71UyEAKRBR1Abo,46
1298
1298
  vellum/utils/templating/render.py,sha256=P2t9qU4w_WdXAVLM5Nj3bc1-XlIKOkwK-czQ80pHBag,2172
1299
1299
  vellum/utils/templating/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1301,7 +1301,7 @@ vellum/utils/templating/tests/test_custom_filters.py,sha256=mkJwc7t1gE13SKgPxhF-
1301
1301
  vellum/utils/typing.py,sha256=wx_daFqD69cYkuJTVnvNrpjhqC3uuhbnyJ9_bIwC9OU,327
1302
1302
  vellum/utils/uuid.py,sha256=Ch6wWRgwICxLxJCTl5iE3EdRlZj2zADR-zUMUtjcMWM,214
1303
1303
  vellum/version.py,sha256=jq-1PlAYxN9AXuaZqbYk9ak27SgE2lw9Ia5gx1b1gVI,76
1304
- vellum/workflows/README.md,sha256=MLNm-ihc0ao6I8gwwOhXQQBf0jOf-EsA9C519ALYI1o,3610
1304
+ vellum/workflows/README.md,sha256=hZdTKBIcsTKPofK68oPkBhyt0nnRh0csqC12k4FMHHA,3597
1305
1305
  vellum/workflows/__init__.py,sha256=CssPsbNvN6rDhoLuqpEv7MMKGa51vE6dvAh6U31Pcio,71
1306
1306
  vellum/workflows/constants.py,sha256=2yg4_uo5gpqViy3ZLSwfC8qTybleYCtOnhA4Rj6bacM,1310
1307
1307
  vellum/workflows/context.py,sha256=DwSf8lO9NHABiqOoD3exgrjUoRuNsKtutaL5TgRbD-A,1441
@@ -1390,19 +1390,19 @@ vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TI
1390
1390
  vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=fNgDufkIsrTC-6ftvogqSpWhqqBj9iNESdfK19B1Yx0,5159
1391
1391
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1392
1392
  vellum/workflows/nodes/core/templating_node/node.py,sha256=iqBmr2i-f-BqhisNQJiDfewjol0ur7-XpupLStyMJsg,3731
1393
- vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=MHofz-BwAgt7EXkab8VIyacYznDEIJ7Er7MJUaxNQQo,9614
1393
+ vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=nXkgGDBg4wP36NwykdMEVWwx_xjv8oGT2rYkwuCB_VU,10075
1394
1394
  vellum/workflows/nodes/core/try_node/__init__.py,sha256=JVD4DrldTIqFQQFrubs9KtWCCc0YCAc7Fzol5ZWIWeM,56
1395
1395
  vellum/workflows/nodes/core/try_node/node.py,sha256=RbxL0NRXS0IxRP0MJAnLABolF6dkwVniiqsagzy-lwk,4445
1396
1396
  vellum/workflows/nodes/core/try_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1397
1397
  vellum/workflows/nodes/core/try_node/tests/test_node.py,sha256=h6eUc3SggvhzBWlOD0PrPUlkoCSQHwjqYn81VkxSIxU,4948
1398
1398
  vellum/workflows/nodes/displayable/__init__.py,sha256=6F_4DlSwvHuilWnIalp8iDjjDXl0Nmz4QzJV2PYe5RI,1023
1399
1399
  vellum/workflows/nodes/displayable/api_node/__init__.py,sha256=MoxdQSnidIj1Nf_d-hTxlOxcZXaZnsWFDbE-PkTK24o,56
1400
- vellum/workflows/nodes/displayable/api_node/node.py,sha256=QdpsyGVxo5PcN8nwGZpcpW_YMKHr3_VvmbK1BlrdOFk,2547
1400
+ vellum/workflows/nodes/displayable/api_node/node.py,sha256=cp0nAukcOpM6TcNhbz12h08TMJxp_LM-MLDl1dAzYsk,2534
1401
1401
  vellum/workflows/nodes/displayable/api_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1402
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=fiskhfcI4c6CxFlbSWb1JKsuq-98zAeUWOExc848ALw,3130
1402
+ vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=J21H1dQT0BJ0oAalaA-9mgKv-NRcCJaTImhnKXp-cX4,3294
1403
1403
  vellum/workflows/nodes/displayable/bases/__init__.py,sha256=0mWIx3qUrzllV7jqt7wN03vWGMuI1WrrLZeMLT2Cl2c,304
1404
1404
  vellum/workflows/nodes/displayable/bases/api_node/__init__.py,sha256=1jwx4WC358CLA1jgzl_UD-rZmdMm2v9Mps39ndwCD7U,64
1405
- vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256=Ev_So7D_4Qfvl2_E8veVfxAxWfbJIA2ujyW5istLg5I,4066
1405
+ vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256=70pLGU0UzWvSbKwNkx3YlUYrDSkl7MmhVHoI8bzN79c,4343
1406
1406
  vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org3xTvgp1pA0uUXFfnJr29D3HzCey2lEdYF4zbIUgo,70
1407
1407
  vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=HGNoGLJ9lbqflGdYFDIiuHFyi0iJ-agJu4kkJ7D3dGs,3212
1408
1408
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py,sha256=Hl35IAoepRpE-j4cALaXVJIYTYOF3qszyVbxTj4kS1s,82
@@ -1462,8 +1462,8 @@ vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py,sha256=c
1462
1462
  vellum/workflows/nodes/mocks.py,sha256=a1FjWEIocseMfjzM-i8DNozpUsaW0IONRpZmXBoWlyc,10455
1463
1463
  vellum/workflows/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1464
1464
  vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9TmQxpdyFKDdO60,7837
1465
- vellum/workflows/nodes/tests/test_utils.py,sha256=7PtdV_fzl56agx0IDitdqOmqUO9cQZmJww-3ToxzSqA,4174
1466
- vellum/workflows/nodes/utils.py,sha256=b-U8xjUpGswaoEiav5tU_OFKB26GkYFzuko9XCMU_Fo,7627
1465
+ vellum/workflows/nodes/tests/test_utils.py,sha256=qNB6ApIsnFtE_9HDaEah9KD-zX8e10FhDixewS1uRcc,5199
1466
+ vellum/workflows/nodes/utils.py,sha256=Tc3TAmAytb-gi30BAyzGY7DG0uS1_7KTGYdjrvKUSt0,8362
1467
1467
  vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
1468
1468
  vellum/workflows/outputs/base.py,sha256=b4Dnha1miKu3uFJYptKKflIHNuajPF2BNKy0BTt8Tjc,8622
1469
1469
  vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
@@ -1522,8 +1522,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1522
1522
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1523
1523
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=NRteiICyJvDM5zrtUfq2fZoXcGQVaWC9xmNlLLVW0cU,7979
1524
1524
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1525
- vellum_ai-0.14.20.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1526
- vellum_ai-0.14.20.dist-info/METADATA,sha256=S1TOATNmyIpyOm-Jhj28lJQ-hYaJUtQ7FZMmfVo2Neo,5408
1527
- vellum_ai-0.14.20.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1528
- vellum_ai-0.14.20.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1529
- vellum_ai-0.14.20.dist-info/RECORD,,
1525
+ vellum_ai-0.14.22.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1526
+ vellum_ai-0.14.22.dist-info/METADATA,sha256=n1LtcR20giCKtaq1b6qilNyA4MKvG4EON_hG8EC8A9U,5484
1527
+ vellum_ai-0.14.22.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1528
+ vellum_ai-0.14.22.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1529
+ vellum_ai-0.14.22.dist-info/RECORD,,
@@ -59,10 +59,6 @@ class EdgeDisplayOverrides(EdgeDisplay):
59
59
  pass
60
60
 
61
61
 
62
- EdgeDisplayType = TypeVar("EdgeDisplayType", bound=EdgeDisplay)
63
- EdgeDisplayOverridesType = TypeVar("EdgeDisplayOverridesType", bound=EdgeDisplayOverrides)
64
-
65
-
66
62
  @dataclass
67
63
  class EntrypointDisplayOverrides:
68
64
  id: UUID
@@ -14,15 +14,6 @@ _APINodeType = TypeVar("_APINodeType", bound=APINode)
14
14
 
15
15
 
16
16
  class BaseAPINodeDisplay(BaseNodeVellumDisplay[_APINodeType], Generic[_APINodeType]):
17
- url_input_id: ClassVar[Optional[UUID]] = None
18
- method_input_id: ClassVar[Optional[UUID]] = None
19
- body_input_id: ClassVar[Optional[UUID]] = None
20
-
21
- authorization_type_input_id: ClassVar[Optional[UUID]] = None
22
- bearer_token_value_input_id: ClassVar[Optional[UUID]] = None
23
- api_key_header_key_input_id: ClassVar[Optional[UUID]] = None
24
- api_key_header_value_input_id: ClassVar[Optional[UUID]] = None
25
-
26
17
  # A mapping between node input keys and their ids for inputs representing additional header keys
27
18
  additional_header_key_input_ids: ClassVar[Optional[Dict[str, UUID]]] = None
28
19
 
@@ -41,7 +32,7 @@ class BaseAPINodeDisplay(BaseNodeVellumDisplay[_APINodeType], Generic[_APINodeTy
41
32
  input_name="url",
42
33
  value=node_url,
43
34
  display_context=display_context,
44
- input_id=self.url_input_id,
35
+ input_id=self.node_input_ids_by_name.get(APINode.url.name),
45
36
  )
46
37
 
47
38
  node_method = raise_if_descriptor(node.method)
@@ -50,7 +41,7 @@ class BaseAPINodeDisplay(BaseNodeVellumDisplay[_APINodeType], Generic[_APINodeTy
50
41
  input_name="method",
51
42
  value=node_method,
52
43
  display_context=display_context,
53
- input_id=self.method_input_id,
44
+ input_id=self.node_input_ids_by_name.get(APINode.method.name),
54
45
  )
55
46
 
56
47
  node_data = raise_if_descriptor(node.data)
@@ -60,7 +51,9 @@ class BaseAPINodeDisplay(BaseNodeVellumDisplay[_APINodeType], Generic[_APINodeTy
60
51
  input_name="body",
61
52
  value=node_data if node_data else node_json,
62
53
  display_context=display_context,
63
- input_id=self.body_input_id,
54
+ input_id=self.node_input_ids_by_name.get(APINode.json.name)
55
+ # Kept for backwards compatibility with a bug in previous versions of SDK serialization
56
+ or self.node_input_ids_by_name.get("body"),
64
57
  )
65
58
 
66
59
  headers = raise_if_descriptor(node.headers)
@@ -75,7 +68,7 @@ class BaseAPINodeDisplay(BaseNodeVellumDisplay[_APINodeType], Generic[_APINodeTy
75
68
  input_name="authorization_type",
76
69
  value=authorization_type,
77
70
  display_context=display_context,
78
- input_id=self.authorization_type_input_id,
71
+ input_id=self.node_input_ids_by_name.get(APINode.authorization_type.name),
79
72
  )
80
73
  if authorization_type
81
74
  else None
@@ -85,7 +78,7 @@ class BaseAPINodeDisplay(BaseNodeVellumDisplay[_APINodeType], Generic[_APINodeTy
85
78
  input_name="bearer_token_value",
86
79
  value=bearer_token_value,
87
80
  display_context=display_context,
88
- input_id=self.bearer_token_value_input_id,
81
+ input_id=self.node_input_ids_by_name.get(APINode.bearer_token_value.name),
89
82
  pointer_type=WorkspaceSecretPointer,
90
83
  )
91
84
  api_key_header_key_node_input = (
@@ -94,7 +87,7 @@ class BaseAPINodeDisplay(BaseNodeVellumDisplay[_APINodeType], Generic[_APINodeTy
94
87
  input_name="api_key_header_key",
95
88
  value=api_key_header_key,
96
89
  display_context=display_context,
97
- input_id=self.api_key_header_key_input_id,
90
+ input_id=self.node_input_ids_by_name.get(APINode.api_key_header_key.name),
98
91
  )
99
92
  if api_key_header_key
100
93
  else None
@@ -104,7 +97,7 @@ class BaseAPINodeDisplay(BaseNodeVellumDisplay[_APINodeType], Generic[_APINodeTy
104
97
  input_name="api_key_header_value",
105
98
  value=api_key_header_value,
106
99
  display_context=display_context,
107
- input_id=self.api_key_header_value_input_id,
100
+ input_id=self.node_input_ids_by_name.get(APINode.api_key_header_value.name),
108
101
  pointer_type=WorkspaceSecretPointer,
109
102
  )
110
103
 
@@ -3,6 +3,7 @@ import types
3
3
  from uuid import UUID
4
4
  from typing import Any, Callable, Dict, Generic, Optional, Type, TypeVar, cast
5
5
 
6
+ from vellum.workflows.nodes.bases.base import BaseNode
6
7
  from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
7
8
  from vellum.workflows.nodes.utils import get_wrapped_node
8
9
  from vellum.workflows.types.core import JsonArray, JsonObject
@@ -17,7 +18,62 @@ from vellum_ee.workflows.display.types import WorkflowDisplayContext
17
18
  _BaseAdornmentNodeType = TypeVar("_BaseAdornmentNodeType", bound=BaseAdornmentNode)
18
19
 
19
20
 
21
+ def _recursively_replace_wrapped_node(node_class: Type[BaseNode], wrapped_node_display_class: Type["BaseNodeDisplay"]):
22
+ if not issubclass(node_class, BaseAdornmentNode):
23
+ return
24
+
25
+ # We must edit the node display class to use __wrapped_node__ everywhere it
26
+ # references the adorned node class, which is three places:
27
+ wrapped_node_class = node_class.__wrapped_node__
28
+ if not wrapped_node_class:
29
+ raise ValueError(f"Node {node_class.__name__} must be used as an adornment with the `wrap` method.")
30
+
31
+ # 1. The node display class' parameterized type
32
+ original_base_node_display = get_original_base(wrapped_node_display_class)
33
+ original_base_node_display.__args__ = (wrapped_node_class,)
34
+ wrapped_node_display_class._node_display_registry[wrapped_node_class] = wrapped_node_display_class
35
+ wrapped_node_display_class.__annotate_node__()
36
+
37
+ # 2. The node display class' output displays
38
+ for old_output in node_class.Outputs:
39
+ new_output = getattr(wrapped_node_class.Outputs, old_output.name, None)
40
+ if new_output is None:
41
+ # If the adornment is adding a new output, such as TryNode adding an "error" output,
42
+ # we skip it, since it should not be included in the adorned node's output displays
43
+ wrapped_node_display_class.output_display.pop(old_output, None)
44
+ continue
45
+
46
+ if old_output not in wrapped_node_display_class.output_display:
47
+ # If the adorned node doesn't have an output display defined for this output, we define one
48
+ wrapped_node_display_class.output_display[new_output] = NodeOutputDisplay(
49
+ id=wrapped_node_class.__output_ids__[old_output.name],
50
+ name=old_output.name,
51
+ )
52
+ else:
53
+ wrapped_node_display_class.output_display[new_output] = wrapped_node_display_class.output_display.pop(
54
+ old_output
55
+ )
56
+
57
+ # 3. The node display class' port displays
58
+ old_ports = list(wrapped_node_display_class.port_displays.keys())
59
+ for old_port in old_ports:
60
+ new_port = getattr(wrapped_node_class.Ports, old_port.name)
61
+ wrapped_node_display_class.port_displays[new_port] = wrapped_node_display_class.port_displays.pop(old_port)
62
+
63
+ # Now we keep drilling down recursively
64
+ if (
65
+ issubclass(wrapped_node_display_class, BaseAdornmentNodeDisplay)
66
+ and wrapped_node_display_class.__wrapped_node_display__
67
+ ):
68
+ _recursively_replace_wrapped_node(
69
+ wrapped_node_class,
70
+ wrapped_node_display_class.__wrapped_node_display__,
71
+ )
72
+
73
+
20
74
  class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Generic[_BaseAdornmentNodeType]):
75
+ __wrapped_node_display__: Optional[Type[BaseNodeDisplay]] = None
76
+
21
77
  def serialize(
22
78
  self,
23
79
  display_context: "WorkflowDisplayContext",
@@ -52,10 +108,6 @@ class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Ge
52
108
  if not issubclass(node_class, BaseAdornmentNode):
53
109
  raise ValueError(f"Node {node_class.__name__} must be wrapped with a {BaseAdornmentNode.__name__}")
54
110
 
55
- wrapped_node_class = node_class.__wrapped_node__
56
- if not wrapped_node_class:
57
- raise ValueError(f"Node {node_class.__name__} must be used as an adornment with the `wrap` method.")
58
-
59
111
  # `mypy` is wrong here, `cls` is indexable bc it's Generic
60
112
  BaseAdornmentDisplay = cls[node_class] # type: ignore[index]
61
113
 
@@ -64,6 +116,7 @@ class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Ge
64
116
  ns[key] = kwarg
65
117
 
66
118
  ns["node_id"] = node_id or uuid4_from_hash(node_class.__qualname__)
119
+ ns["__wrapped_node_display__"] = wrapped_node_display_class
67
120
 
68
121
  AdornmentDisplay = types.new_class(
69
122
  re.sub(r"^Base", "", cls.__name__), bases=(BaseAdornmentDisplay,), exec_body=exec_body
@@ -71,43 +124,11 @@ class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Ge
71
124
 
72
125
  setattr(wrapped_node_display_class, "__adorned_by__", AdornmentDisplay)
73
126
 
74
- # We must edit the node display class to use __wrapped_node__ everywhere it
75
- # references the adorned node class, which is three places:
76
-
77
- # 1. The node display class' parameterized type
78
- original_base_node_display = get_original_base(wrapped_node_display_class)
79
- original_base_node_display.__args__ = (wrapped_node_class,)
80
- wrapped_node_display_class._node_display_registry[wrapped_node_class] = wrapped_node_display_class
81
- wrapped_node_display_class.__annotate_node__()
82
-
83
- # 2. The node display class' output displays
84
- for old_output in node_class.Outputs:
85
- new_output = getattr(wrapped_node_class.Outputs, old_output.name, None)
86
- if new_output is None:
87
- # If the adornment is adding a new output, such as TryNode adding an "error" output,
88
- # we skip it, since it should not be included in the adorned node's output displays
89
- wrapped_node_display_class.output_display.pop(old_output, None)
90
- continue
91
-
92
- if old_output not in wrapped_node_display_class.output_display:
93
- # If the adorned node doesn't have an output display defined for this output, we define one
94
- wrapped_node_display_class.output_display[new_output] = NodeOutputDisplay(
95
- id=wrapped_node_class.__output_ids__[old_output.name],
96
- name=old_output.name,
97
- )
98
- else:
99
- wrapped_node_display_class.output_display[new_output] = (
100
- wrapped_node_display_class.output_display.pop(old_output)
101
- )
102
-
103
- # 3. The node display class' port displays
104
- old_ports = list(wrapped_node_display_class.port_displays.keys())
105
- for old_port in old_ports:
106
- new_port = getattr(wrapped_node_class.Ports, old_port.name)
107
- wrapped_node_display_class.port_displays[new_port] = wrapped_node_display_class.port_displays.pop(
108
- old_port
109
- )
110
-
111
- return wrapped_node_display_class
127
+ _recursively_replace_wrapped_node(
128
+ node_class,
129
+ wrapped_node_display_class,
130
+ )
131
+
132
+ return AdornmentDisplay
112
133
 
113
134
  return decorator
@@ -6,7 +6,6 @@ from vellum.workflows.types.core import JsonObject
6
6
  from vellum.workflows.utils.uuids import uuid4_from_hash
7
7
  from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
8
8
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
9
- from vellum_ee.workflows.display.vellum import EdgeVellumDisplay
10
9
 
11
10
  _MergeNodeType = TypeVar("_MergeNodeType", bound=MergeNode)
12
11
 
@@ -22,16 +21,20 @@ class BaseMergeNodeDisplay(BaseNodeVellumDisplay[_MergeNodeType], Generic[_Merge
22
21
  node = self._node
23
22
  node_id = self.node_id
24
23
 
25
- all_edges: List[EdgeVellumDisplay] = [edge_display for _, edge_display in display_context.edge_displays.items()]
26
- merged_edges = [edge for edge in all_edges if edge.target_node_id == self.node_id]
24
+ merged_source_ports = [
25
+ source_node_port
26
+ for (source_node_port, target_node), _ in display_context.edge_displays.items()
27
+ if target_node.__id__ == self.node_id
28
+ ]
27
29
 
28
30
  target_handle_ids = self.get_target_handle_ids()
29
31
 
30
32
  if target_handle_ids is None:
31
33
  target_handle_ids = [
32
- uuid4_from_hash(f"{node_id}|target_handle|{edge.source_node_id}") for edge in merged_edges
34
+ uuid4_from_hash(f"{node_id}|target_handle|{source_node_port.node_class.__id__}")
35
+ for source_node_port in merged_source_ports
33
36
  ]
34
- elif len(target_handle_ids) != len(merged_edges):
37
+ elif len(target_handle_ids) != len(merged_source_ports):
35
38
  raise ValueError("If you explicitly specify target_handle_ids, you must specify one for each incoming edge")
36
39
 
37
40
  return {
@@ -7,6 +7,7 @@ from vellum.workflows.nodes import BaseNode
7
7
  from vellum.workflows.ports import Port
8
8
  from vellum.workflows.references import OutputReference, StateValueReference, WorkflowInputReference
9
9
  from vellum_ee.workflows.display.base import (
10
+ EdgeDisplay,
10
11
  EntrypointDisplayType,
11
12
  StateValueDisplayType,
12
13
  WorkflowInputsDisplayType,
@@ -17,7 +18,6 @@ from vellum_ee.workflows.display.base import (
17
18
  if TYPE_CHECKING:
18
19
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
19
20
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay
20
- from vellum_ee.workflows.display.vellum import EdgeVellumDisplay
21
21
  from vellum_ee.workflows.display.workflows import BaseWorkflowDisplay
22
22
 
23
23
  WorkflowDisplayType = TypeVar("WorkflowDisplayType", bound="BaseWorkflowDisplay")
@@ -47,5 +47,5 @@ class WorkflowDisplayContext(
47
47
  )
48
48
  entrypoint_displays: Dict[Type[BaseNode], EntrypointDisplayType] = field(default_factory=dict)
49
49
  workflow_output_displays: Dict[BaseDescriptor, WorkflowOutputDisplay] = field(default_factory=dict)
50
- edge_displays: Dict[Tuple[Port, Type[BaseNode]], "EdgeVellumDisplay"] = field(default_factory=dict)
50
+ edge_displays: Dict[Tuple[Port, Type[BaseNode]], EdgeDisplay] = field(default_factory=dict)
51
51
  port_displays: Dict[Port, "PortDisplay"] = field(default_factory=dict)
@@ -121,7 +121,7 @@ class EntrypointVellumDisplayOverrides(EntrypointDisplay, EntrypointDisplayOverr
121
121
 
122
122
  @dataclass
123
123
  class EntrypointVellumDisplay(EntrypointVellumDisplayOverrides):
124
- edge_display: EdgeVellumDisplay
124
+ edge_display: EdgeDisplay
125
125
 
126
126
 
127
127
  @dataclass
@@ -12,7 +12,7 @@ from vellum.workflows.edges import Edge
12
12
  from vellum.workflows.events.workflow import NodeEventDisplayContext, WorkflowEventDisplayContext
13
13
  from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
14
14
  from vellum.workflows.nodes.bases import BaseNode
15
- from vellum.workflows.nodes.utils import get_unadorned_node, get_unadorned_port, get_wrapped_node
15
+ from vellum.workflows.nodes.utils import get_unadorned_node, get_wrapped_node
16
16
  from vellum.workflows.ports import Port
17
17
  from vellum.workflows.references import OutputReference, StateValueReference, WorkflowInputReference
18
18
  from vellum.workflows.types.core import JsonObject
@@ -36,7 +36,6 @@ from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_di
36
36
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
37
37
  from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
38
38
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
39
- from vellum_ee.workflows.display.vellum import EdgeVellumDisplay
40
39
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
41
40
 
42
41
  logger = logging.getLogger(__name__)
@@ -151,8 +150,6 @@ class BaseWorkflowDisplay(
151
150
  if node_output in node_output_displays:
152
151
  continue
153
152
 
154
- # TODO: Make sure this output ID matches the workflow output ID of the subworkflow node's workflow
155
- # https://app.shortcut.com/vellum/story/5660/fix-output-id-in-subworkflow-nodes
156
153
  node_output_displays[node_output] = node_display.get_node_output_display(node_output)
157
154
 
158
155
  def _enrich_node_port_displays(
@@ -267,14 +264,14 @@ class BaseWorkflowDisplay(
267
264
  entrypoint, workflow_display, node_displays, overrides=entrypoint_display_overrides
268
265
  )
269
266
 
270
- edge_displays: Dict[Tuple[Port, Type[BaseNode]], EdgeVellumDisplay] = {}
267
+ edge_displays: Dict[Tuple[Port, Type[BaseNode]], EdgeDisplay] = {}
271
268
  for edge in self._workflow.get_edges():
272
269
  if edge in edge_displays:
273
270
  continue
274
271
 
275
272
  edge_display_overrides = self.edge_displays.get((edge.from_port, edge.to_node))
276
- edge_displays[(edge.from_port, edge.to_node)] = self._generate_edge_display(
277
- edge, node_displays, port_displays, overrides=edge_display_overrides
273
+ edge_displays[(edge.from_port, edge.to_node)] = edge_display_overrides or self._generate_edge_display(
274
+ edge, node_displays
278
275
  )
279
276
 
280
277
  for edge in self._workflow.get_unused_edges():
@@ -282,8 +279,8 @@ class BaseWorkflowDisplay(
282
279
  continue
283
280
 
284
281
  edge_display_overrides = self.edge_displays.get((edge.from_port, edge.to_node))
285
- edge_displays[(edge.from_port, edge.to_node)] = self._generate_edge_display(
286
- edge, node_displays, port_displays, overrides=edge_display_overrides
282
+ edge_displays[(edge.from_port, edge.to_node)] = edge_display_overrides or self._generate_edge_display(
283
+ edge, node_displays
287
284
  )
288
285
 
289
286
  workflow_output_displays: Dict[BaseDescriptor, WorkflowOutputDisplay] = {}
@@ -444,46 +441,20 @@ class BaseWorkflowDisplay(
444
441
 
445
442
  return additional_node_displays
446
443
 
447
- def _generate_edge_display(
448
- self,
449
- edge: Edge,
450
- node_displays: Dict[Type[BaseNode], BaseNodeDisplay],
451
- port_displays: Dict[Port, PortDisplay],
452
- overrides: Optional[EdgeDisplay] = None,
453
- ) -> EdgeVellumDisplay:
444
+ def _generate_edge_display(self, edge: Edge, node_displays: Dict[Type[BaseNode], BaseNodeDisplay]) -> EdgeDisplay:
454
445
  source_node = get_unadorned_node(edge.from_port.node_class)
455
446
  target_node = get_unadorned_node(edge.to_node)
456
447
 
457
448
  source_node_id = node_displays[source_node].node_id
458
- from_port = get_unadorned_port(edge.from_port)
459
- source_handle_id = port_displays[from_port].id
449
+ target_node_id = node_displays[target_node].node_id
460
450
 
461
- target_node_display = node_displays[target_node]
462
- target_node_id = target_node_display.node_id
463
- target_handle_id = target_node_display.get_target_handle_id_by_source_node_id(source_node_id)
464
-
465
- return self._generate_edge_display_from_source(
466
- source_node_id, source_handle_id, target_node_id, target_handle_id, overrides
467
- )
451
+ return self._generate_edge_display_from_source(source_node_id, target_node_id)
468
452
 
469
453
  def _generate_edge_display_from_source(
470
454
  self,
471
455
  source_node_id: UUID,
472
- source_handle_id: UUID,
473
456
  target_node_id: UUID,
474
- target_handle_id: UUID,
475
- overrides: Optional[EdgeDisplay] = None,
476
- ) -> EdgeVellumDisplay:
477
- edge_id: UUID
478
- if overrides:
479
- edge_id = overrides.id
480
- else:
481
- edge_id = uuid4_from_hash(f"{self.workflow_id}|id|{source_node_id}|{target_node_id}")
482
-
483
- return EdgeVellumDisplay(
484
- id=edge_id,
485
- source_node_id=source_node_id,
486
- target_node_id=target_node_id,
487
- source_handle_id=source_handle_id,
488
- target_handle_id=target_handle_id,
457
+ ) -> EdgeDisplay:
458
+ return EdgeDisplay(
459
+ id=uuid4_from_hash(f"{self.workflow_id}|id|{source_node_id}|{target_node_id}"),
489
460
  )
@@ -235,3 +235,43 @@ def test_get_event_display_context__templating_node_input_display():
235
235
  node_event_display = display_context.node_displays[MyNode.__id__]
236
236
 
237
237
  assert node_event_display.input_display.keys() == {"inputs.foo"}
238
+
239
+
240
+ def test_get_event_display_context__node_display_for_mutiple_adornments():
241
+ # GIVEN a simple workflow with multiple adornments
242
+ @TryNode.wrap()
243
+ @RetryNode.wrap()
244
+ class MyNode(BaseNode):
245
+ class Outputs(BaseNode.Outputs):
246
+ foo: str
247
+
248
+ class MyWorkflow(BaseWorkflow):
249
+ graph = MyNode
250
+
251
+ # AND a display class for the node
252
+ node_id = uuid4()
253
+ inner_node_id = uuid4()
254
+ innermost_node_id = uuid4()
255
+
256
+ @BaseTryNodeDisplay.wrap(node_id=node_id)
257
+ @BaseRetryNodeDisplay.wrap(node_id=inner_node_id)
258
+ class MyNodeDisplay(BaseNodeDisplay[MyNode]):
259
+ node_id = innermost_node_id
260
+
261
+ # WHEN we gather the event display context
262
+ display_context = VellumWorkflowDisplay(MyWorkflow).get_event_display_context()
263
+
264
+ # THEN the subworkflow display should be included
265
+ assert node_id in display_context.node_displays
266
+ node_event_display = display_context.node_displays[node_id]
267
+ assert node_event_display.subworkflow_display
268
+
269
+ # AND the inner node should be included
270
+ assert inner_node_id in node_event_display.subworkflow_display.node_displays
271
+ inner_node_event_display = node_event_display.subworkflow_display.node_displays[inner_node_id]
272
+ assert inner_node_event_display.subworkflow_display
273
+
274
+ # AND the innermost node should be included
275
+ assert innermost_node_id in inner_node_event_display.subworkflow_display.node_displays
276
+ innermost_node_event_display = inner_node_event_display.subworkflow_display.node_displays[innermost_node_id]
277
+ assert not innermost_node_event_display.subworkflow_display
@@ -351,7 +351,6 @@ class VellumWorkflowDisplay(
351
351
  overrides: Optional[EntrypointVellumDisplayOverrides] = None,
352
352
  ) -> EntrypointVellumDisplay:
353
353
  entrypoint_node_id = workflow_display.entrypoint_node_id
354
- source_handle_id = workflow_display.entrypoint_node_source_handle_id
355
354
 
356
355
  edge_display_overrides = overrides.edge_display if overrides else None
357
356
  entrypoint_id = (
@@ -363,10 +362,9 @@ class VellumWorkflowDisplay(
363
362
  entrypoint_target = get_unadorned_node(entrypoint)
364
363
  target_node_display = node_displays[entrypoint_target]
365
364
  target_node_id = target_node_display.node_id
366
- target_handle_id = target_node_display.get_target_handle_id_by_source_node_id(entrypoint_node_id)
367
365
 
368
- edge_display = self._generate_edge_display_from_source(
369
- entrypoint_node_id, source_handle_id, target_node_id, target_handle_id, overrides=edge_display_overrides
366
+ edge_display = edge_display_overrides or self._generate_edge_display_from_source(
367
+ entrypoint_node_id, target_node_id
370
368
  )
371
369
 
372
370
  return EntrypointVellumDisplay(id=entrypoint_id, edge_display=edge_display)
@@ -1,7 +1,7 @@
1
1
  from uuid import UUID
2
2
 
3
+ from vellum_ee.workflows.display.base import EdgeDisplay
3
4
  from vellum_ee.workflows.display.vellum import (
4
- EdgeVellumDisplayOverrides,
5
5
  EntrypointVellumDisplayOverrides,
6
6
  NodeDisplayData,
7
7
  NodeDisplayPosition,
@@ -36,13 +36,11 @@ class WorkflowDisplay(VellumWorkflowDisplay[Workflow]):
36
36
  entrypoint_displays = {
37
37
  TemplatingNode: EntrypointVellumDisplayOverrides(
38
38
  id=UUID("0bf86989-13f2-438c-ab9c-d172e5771d31"),
39
- edge_display=EdgeVellumDisplayOverrides(id=UUID("38532a0e-9432-4ed2-8a34-48a29fd6984d")),
39
+ edge_display=EdgeDisplay(id=UUID("38532a0e-9432-4ed2-8a34-48a29fd6984d")),
40
40
  )
41
41
  }
42
42
  edge_displays = {
43
- (TemplatingNode.Ports.default, FinalOutput): EdgeVellumDisplayOverrides(
44
- id=UUID("417c56a4-cdc1-4f9d-a10c-b535163f51e8")
45
- )
43
+ (TemplatingNode.Ports.default, FinalOutput): EdgeDisplay(id=UUID("417c56a4-cdc1-4f9d-a10c-b535163f51e8"))
46
44
  }
47
45
  output_displays = {
48
46
  Workflow.Outputs.final_output: WorkflowOutputVellumDisplayOverrides(