vellum-ai 0.12.4__py3-none-any.whl → 0.12.6__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 (30) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/client/resources/workflows/client.py +32 -0
  3. vellum/client/types/chat_message_prompt_block.py +1 -1
  4. vellum/client/types/function_definition.py +26 -7
  5. vellum/client/types/jinja_prompt_block.py +1 -1
  6. vellum/client/types/plain_text_prompt_block.py +1 -1
  7. vellum/client/types/rich_text_prompt_block.py +1 -1
  8. vellum/client/types/variable_prompt_block.py +1 -1
  9. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +13 -4
  10. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +55 -0
  11. vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +1 -1
  12. vellum/workflows/sandbox.py +51 -0
  13. vellum/workflows/tests/__init__.py +0 -0
  14. vellum/workflows/tests/test_sandbox.py +62 -0
  15. vellum/workflows/utils/functions.py +41 -4
  16. vellum/workflows/utils/tests/test_functions.py +93 -0
  17. {vellum_ai-0.12.4.dist-info → vellum_ai-0.12.6.dist-info}/METADATA +1 -1
  18. {vellum_ai-0.12.4.dist-info → vellum_ai-0.12.6.dist-info}/RECORD +30 -27
  19. vellum_cli/__init__.py +14 -0
  20. vellum_cli/pull.py +16 -2
  21. vellum_cli/tests/test_pull.py +45 -0
  22. vellum_ee/workflows/display/nodes/base_node_vellum_display.py +2 -2
  23. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -3
  24. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +18 -18
  25. vellum_ee/workflows/display/nodes/vellum/search_node.py +19 -15
  26. vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -1
  27. vellum_ee/workflows/display/nodes/vellum/utils.py +4 -4
  28. {vellum_ai-0.12.4.dist-info → vellum_ai-0.12.6.dist-info}/LICENSE +0 -0
  29. {vellum_ai-0.12.4.dist-info → vellum_ai-0.12.6.dist-info}/WHEEL +0 -0
  30. {vellum_ai-0.12.4.dist-info → vellum_ai-0.12.6.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.12.4",
21
+ "X-Fern-SDK-Version": "0.12.6",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -27,7 +27,11 @@ class WorkflowsClient:
27
27
  self,
28
28
  id: str,
29
29
  *,
30
+ exclude_code: typing.Optional[bool] = None,
30
31
  format: typing.Optional[WorkflowsPullRequestFormat] = None,
32
+ include_json: typing.Optional[bool] = None,
33
+ include_sandbox: typing.Optional[bool] = None,
34
+ strict: typing.Optional[bool] = None,
31
35
  request_options: typing.Optional[RequestOptions] = None,
32
36
  ) -> typing.Iterator[bytes]:
33
37
  """
@@ -38,8 +42,16 @@ class WorkflowsClient:
38
42
  id : str
39
43
  The ID of the Workflow to pull from
40
44
 
45
+ exclude_code : typing.Optional[bool]
46
+
41
47
  format : typing.Optional[WorkflowsPullRequestFormat]
42
48
 
49
+ include_json : typing.Optional[bool]
50
+
51
+ include_sandbox : typing.Optional[bool]
52
+
53
+ strict : typing.Optional[bool]
54
+
43
55
  request_options : typing.Optional[RequestOptions]
44
56
  Request-specific configuration.
45
57
 
@@ -53,7 +65,11 @@ class WorkflowsClient:
53
65
  base_url=self._client_wrapper.get_environment().default,
54
66
  method="GET",
55
67
  params={
68
+ "exclude_code": exclude_code,
56
69
  "format": format,
70
+ "include_json": include_json,
71
+ "include_sandbox": include_sandbox,
72
+ "strict": strict,
57
73
  },
58
74
  request_options=request_options,
59
75
  ) as _response:
@@ -164,7 +180,11 @@ class AsyncWorkflowsClient:
164
180
  self,
165
181
  id: str,
166
182
  *,
183
+ exclude_code: typing.Optional[bool] = None,
167
184
  format: typing.Optional[WorkflowsPullRequestFormat] = None,
185
+ include_json: typing.Optional[bool] = None,
186
+ include_sandbox: typing.Optional[bool] = None,
187
+ strict: typing.Optional[bool] = None,
168
188
  request_options: typing.Optional[RequestOptions] = None,
169
189
  ) -> typing.AsyncIterator[bytes]:
170
190
  """
@@ -175,8 +195,16 @@ class AsyncWorkflowsClient:
175
195
  id : str
176
196
  The ID of the Workflow to pull from
177
197
 
198
+ exclude_code : typing.Optional[bool]
199
+
178
200
  format : typing.Optional[WorkflowsPullRequestFormat]
179
201
 
202
+ include_json : typing.Optional[bool]
203
+
204
+ include_sandbox : typing.Optional[bool]
205
+
206
+ strict : typing.Optional[bool]
207
+
180
208
  request_options : typing.Optional[RequestOptions]
181
209
  Request-specific configuration.
182
210
 
@@ -190,7 +218,11 @@ class AsyncWorkflowsClient:
190
218
  base_url=self._client_wrapper.get_environment().default,
191
219
  method="GET",
192
220
  params={
221
+ "exclude_code": exclude_code,
193
222
  "format": format,
223
+ "include_json": include_json,
224
+ "include_sandbox": include_sandbox,
225
+ "strict": strict,
194
226
  },
195
227
  request_options=request_options,
196
228
  ) as _response:
@@ -16,9 +16,9 @@ class ChatMessagePromptBlock(UniversalBaseModel):
16
16
  A block that represents a chat message in a prompt template.
17
17
  """
18
18
 
19
+ block_type: typing.Literal["CHAT_MESSAGE"] = "CHAT_MESSAGE"
19
20
  state: typing.Optional[PromptBlockState] = None
20
21
  cache_config: typing.Optional[EphemeralPromptCacheConfig] = None
21
- block_type: typing.Literal["CHAT_MESSAGE"] = "CHAT_MESSAGE"
22
22
  chat_role: ChatMessageRole
23
23
  chat_source: typing.Optional[str] = None
24
24
  chat_message_unterminated: typing.Optional[bool] = None
@@ -4,22 +4,41 @@ from ..core.pydantic_utilities import UniversalBaseModel
4
4
  import typing
5
5
  from .prompt_block_state import PromptBlockState
6
6
  from .ephemeral_prompt_cache_config import EphemeralPromptCacheConfig
7
- from ..core.pydantic_utilities import IS_PYDANTIC_V2
8
7
  import pydantic
8
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2
9
9
 
10
10
 
11
11
  class FunctionDefinition(UniversalBaseModel):
12
12
  """
13
- A block that represents a function definition in a prompt template.
13
+ The definition of a Function (aka "Tool Call") that a Prompt/Model has access to.
14
14
  """
15
15
 
16
16
  state: typing.Optional[PromptBlockState] = None
17
17
  cache_config: typing.Optional[EphemeralPromptCacheConfig] = None
18
- name: typing.Optional[str] = None
19
- description: typing.Optional[str] = None
20
- parameters: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None
21
- forced: typing.Optional[bool] = None
22
- strict: typing.Optional[bool] = None
18
+ name: typing.Optional[str] = pydantic.Field(default=None)
19
+ """
20
+ The name identifying the function.
21
+ """
22
+
23
+ description: typing.Optional[str] = pydantic.Field(default=None)
24
+ """
25
+ A description to help guide the model when to invoke this function.
26
+ """
27
+
28
+ parameters: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = pydantic.Field(default=None)
29
+ """
30
+ An OpenAPI specification of parameters that are supported by this function.
31
+ """
32
+
33
+ forced: typing.Optional[bool] = pydantic.Field(default=None)
34
+ """
35
+ Set this option to true to force the model to return a function call of this function.
36
+ """
37
+
38
+ strict: typing.Optional[bool] = pydantic.Field(default=None)
39
+ """
40
+ Set this option to use strict schema decoding when available.
41
+ """
23
42
 
24
43
  if IS_PYDANTIC_V2:
25
44
  model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
@@ -13,9 +13,9 @@ class JinjaPromptBlock(UniversalBaseModel):
13
13
  A block of Jinja template code that is used to generate a prompt
14
14
  """
15
15
 
16
+ block_type: typing.Literal["JINJA"] = "JINJA"
16
17
  state: typing.Optional[PromptBlockState] = None
17
18
  cache_config: typing.Optional[EphemeralPromptCacheConfig] = None
18
- block_type: typing.Literal["JINJA"] = "JINJA"
19
19
  template: str
20
20
 
21
21
  if IS_PYDANTIC_V2:
@@ -13,9 +13,9 @@ class PlainTextPromptBlock(UniversalBaseModel):
13
13
  A block that holds a plain text string value.
14
14
  """
15
15
 
16
+ block_type: typing.Literal["PLAIN_TEXT"] = "PLAIN_TEXT"
16
17
  state: typing.Optional[PromptBlockState] = None
17
18
  cache_config: typing.Optional[EphemeralPromptCacheConfig] = None
18
- block_type: typing.Literal["PLAIN_TEXT"] = "PLAIN_TEXT"
19
19
  text: str
20
20
 
21
21
  if IS_PYDANTIC_V2:
@@ -14,9 +14,9 @@ class RichTextPromptBlock(UniversalBaseModel):
14
14
  A block that includes a combination of plain text and variable blocks.
15
15
  """
16
16
 
17
+ block_type: typing.Literal["RICH_TEXT"] = "RICH_TEXT"
17
18
  state: typing.Optional[PromptBlockState] = None
18
19
  cache_config: typing.Optional[EphemeralPromptCacheConfig] = None
19
- block_type: typing.Literal["RICH_TEXT"] = "RICH_TEXT"
20
20
  blocks: typing.List[RichTextChildBlock]
21
21
 
22
22
  if IS_PYDANTIC_V2:
@@ -13,9 +13,9 @@ class VariablePromptBlock(UniversalBaseModel):
13
13
  A block that represents a variable in a prompt template.
14
14
  """
15
15
 
16
+ block_type: typing.Literal["VARIABLE"] = "VARIABLE"
16
17
  state: typing.Optional[PromptBlockState] = None
17
18
  cache_config: typing.Optional[EphemeralPromptCacheConfig] = None
18
- block_type: typing.Literal["VARIABLE"] = "VARIABLE"
19
19
  input_variable: str
20
20
 
21
21
  if IS_PYDANTIC_V2:
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from uuid import uuid4
3
- from typing import ClassVar, Generic, Iterator, List, Optional, Tuple, cast
3
+ from typing import Callable, ClassVar, Generic, Iterator, List, Optional, Tuple, Union, cast
4
4
 
5
5
  from vellum import (
6
6
  AdHocExecutePromptEvent,
@@ -24,9 +24,10 @@ from vellum.workflows.exceptions import NodeException
24
24
  from vellum.workflows.nodes.displayable.bases.base_prompt_node import BasePromptNode
25
25
  from vellum.workflows.nodes.displayable.bases.inline_prompt_node.constants import DEFAULT_PROMPT_PARAMETERS
26
26
  from vellum.workflows.types.generics import StateType
27
+ from vellum.workflows.utils.functions import compile_function_definition
27
28
 
28
29
 
29
- class BaseInlinePromptNode(BasePromptNode, Generic[StateType]):
30
+ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
30
31
  """
31
32
  Used to execute a Prompt defined inline.
32
33
 
@@ -45,7 +46,7 @@ class BaseInlinePromptNode(BasePromptNode, Generic[StateType]):
45
46
  blocks: ClassVar[List[PromptBlock]]
46
47
 
47
48
  # The functions/tools that a Prompt has access to
48
- functions: Optional[List[FunctionDefinition]] = OMIT
49
+ functions: Optional[List[Union[FunctionDefinition, Callable]]] = None
49
50
 
50
51
  parameters: PromptParameters = DEFAULT_PROMPT_PARAMETERS
51
52
  expand_meta: Optional[AdHocExpandMeta] = OMIT
@@ -59,6 +60,14 @@ class BaseInlinePromptNode(BasePromptNode, Generic[StateType]):
59
60
  "execution_context": {"parent_context": parent_context},
60
61
  **request_options.get("additional_body_parameters", {}),
61
62
  }
63
+ normalized_functions = (
64
+ [
65
+ function if isinstance(function, FunctionDefinition) else compile_function_definition(function)
66
+ for function in self.functions
67
+ ]
68
+ if self.functions
69
+ else None
70
+ )
62
71
 
63
72
  return self._context.vellum_client.ad_hoc.adhoc_execute_prompt_stream(
64
73
  ml_model=self.ml_model,
@@ -66,7 +75,7 @@ class BaseInlinePromptNode(BasePromptNode, Generic[StateType]):
66
75
  input_variables=input_variables,
67
76
  parameters=self.parameters,
68
77
  blocks=self.blocks,
69
- functions=self.functions,
78
+ functions=normalized_functions,
70
79
  expand_meta=self.expand_meta,
71
80
  request_options=self.request_options,
72
81
  )
@@ -5,10 +5,14 @@ from typing import Any, Iterator, List
5
5
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
6
6
  from vellum.client.types.execute_prompt_event import ExecutePromptEvent
7
7
  from vellum.client.types.fulfilled_execute_prompt_event import FulfilledExecutePromptEvent
8
+ from vellum.client.types.function_call import FunctionCall
9
+ from vellum.client.types.function_call_vellum_value import FunctionCallVellumValue
10
+ from vellum.client.types.function_definition import FunctionDefinition
8
11
  from vellum.client.types.initiated_execute_prompt_event import InitiatedExecutePromptEvent
9
12
  from vellum.client.types.prompt_output import PromptOutput
10
13
  from vellum.client.types.prompt_request_json_input import PromptRequestJsonInput
11
14
  from vellum.client.types.string_vellum_value import StringVellumValue
15
+ from vellum.workflows.nodes.displayable.bases.inline_prompt_node.node import BaseInlinePromptNode
12
16
  from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
13
17
 
14
18
 
@@ -62,3 +66,54 @@ def test_inline_prompt_node__json_inputs(vellum_adhoc_prompt_client):
62
66
  PromptRequestJsonInput(key="a_pydantic", type="JSON", value={"example": "example"}),
63
67
  ]
64
68
  assert len(mock_api.call_args.kwargs["input_variables"]) == 4
69
+
70
+
71
+ def test_inline_prompt_node__function_definitions(vellum_adhoc_prompt_client):
72
+ # GIVEN a function definition
73
+ def my_function(foo: str, bar: int) -> None:
74
+ pass
75
+
76
+ # AND a prompt node with a accepting that function definition
77
+ class MyNode(BaseInlinePromptNode):
78
+ ml_model = "gpt-4o"
79
+ functions = [my_function]
80
+ prompt_inputs = {}
81
+ blocks = []
82
+
83
+ # AND a known response from invoking an inline prompt
84
+ expected_outputs: List[PromptOutput] = [
85
+ FunctionCallVellumValue(value=FunctionCall(name="my_function", arguments={"foo": "hello", "bar": 1})),
86
+ ]
87
+
88
+ def generate_prompt_events(*args: Any, **kwargs: Any) -> Iterator[ExecutePromptEvent]:
89
+ execution_id = str(uuid4())
90
+ events: List[ExecutePromptEvent] = [
91
+ InitiatedExecutePromptEvent(execution_id=execution_id),
92
+ FulfilledExecutePromptEvent(
93
+ execution_id=execution_id,
94
+ outputs=expected_outputs,
95
+ ),
96
+ ]
97
+ yield from events
98
+
99
+ vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
100
+
101
+ # WHEN the node is run
102
+ list(MyNode().run())
103
+
104
+ # THEN the prompt is executed with the correct inputs
105
+ mock_api = vellum_adhoc_prompt_client.adhoc_execute_prompt_stream
106
+ assert mock_api.call_count == 1
107
+ assert mock_api.call_args.kwargs["functions"] == [
108
+ FunctionDefinition(
109
+ name="my_function",
110
+ parameters={
111
+ "type": "object",
112
+ "properties": {
113
+ "foo": {"type": "string"},
114
+ "bar": {"type": "integer"},
115
+ },
116
+ "required": ["foo", "bar"],
117
+ },
118
+ ),
119
+ ]
@@ -75,7 +75,7 @@ def test_inline_text_prompt_node__basic(vellum_adhoc_prompt_client):
75
75
  vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.assert_called_once_with(
76
76
  blocks=[],
77
77
  expand_meta=Ellipsis,
78
- functions=Ellipsis,
78
+ functions=None,
79
79
  input_values=[],
80
80
  input_variables=[],
81
81
  ml_model="gpt-4o",
@@ -0,0 +1,51 @@
1
+ from typing import Generic, Sequence, Type
2
+
3
+ import dotenv
4
+
5
+ from vellum.workflows.events.workflow import WorkflowEventStream
6
+ from vellum.workflows.inputs.base import BaseInputs
7
+ from vellum.workflows.logging import load_logger
8
+ from vellum.workflows.types.generics import WorkflowType
9
+ from vellum.workflows.workflows.event_filters import root_workflow_event_filter
10
+
11
+
12
+ class SandboxRunner(Generic[WorkflowType]):
13
+ def __init__(self, workflow: Type[WorkflowType], inputs: Sequence[BaseInputs]):
14
+ if not inputs:
15
+ raise ValueError("Inputs are required to have at least one defined inputs")
16
+
17
+ self._workflow = workflow
18
+ self._inputs = inputs
19
+
20
+ dotenv.load_dotenv()
21
+ self._logger = load_logger()
22
+
23
+ def run(self, index: int = 0):
24
+ if index < 0:
25
+ self._logger.warning("Index is less than 0, running first input")
26
+ index = 0
27
+ elif index >= len(self._inputs):
28
+ self._logger.warning("Index is greater than the number of provided inputs, running last input")
29
+ index = len(self._inputs) - 1
30
+
31
+ selected_inputs = self._inputs[index]
32
+
33
+ workflow = self._workflow()
34
+ events = workflow.stream(
35
+ inputs=selected_inputs,
36
+ event_filter=root_workflow_event_filter,
37
+ )
38
+
39
+ self._process_events(events)
40
+
41
+ def _process_events(self, events: WorkflowEventStream):
42
+ for event in events:
43
+ if event.name == "workflow.execution.fulfilled":
44
+ self._logger.info("Workflow fulfilled!")
45
+ for output_reference, value in event.outputs:
46
+ self._logger.info("----------------------------------")
47
+ self._logger.info(f"{output_reference.name}: {value}")
48
+ elif event.name == "node.execution.initiated":
49
+ self._logger.info(f"Just started Node: {event.node_definition.__name__}")
50
+ elif event.name == "node.execution.fulfilled":
51
+ self._logger.info(f"Just finished Node: {event.node_definition.__name__}")
File without changes
@@ -0,0 +1,62 @@
1
+ import pytest
2
+ from typing import List
3
+
4
+ from vellum.workflows.inputs.base import BaseInputs
5
+ from vellum.workflows.nodes.bases.base import BaseNode
6
+ from vellum.workflows.sandbox import SandboxRunner
7
+ from vellum.workflows.state.base import BaseState
8
+ from vellum.workflows.workflows.base import BaseWorkflow
9
+
10
+
11
+ @pytest.fixture
12
+ def mock_logger(mocker):
13
+ return mocker.patch("vellum.workflows.sandbox.load_logger")
14
+
15
+
16
+ @pytest.mark.parametrize(
17
+ ["run_kwargs", "expected_last_log"],
18
+ [
19
+ ({}, "final_results: first"),
20
+ ({"index": 1}, "final_results: second"),
21
+ ({"index": -4}, "final_results: first"),
22
+ ({"index": 100}, "final_results: second"),
23
+ ],
24
+ ids=["default", "specific", "negative", "out_of_bounds"],
25
+ )
26
+ def test_sandbox_runner__happy_path(mock_logger, run_kwargs, expected_last_log):
27
+ # GIVEN we capture the logs to stdout
28
+ logs = []
29
+ mock_logger.return_value.info.side_effect = lambda msg: logs.append(msg)
30
+
31
+ # AND an example workflow
32
+ class Inputs(BaseInputs):
33
+ foo: str
34
+
35
+ class StartNode(BaseNode):
36
+ class Outputs(BaseNode.Outputs):
37
+ bar = Inputs.foo
38
+
39
+ class Workflow(BaseWorkflow[Inputs, BaseState]):
40
+ graph = StartNode
41
+
42
+ class Outputs(BaseWorkflow.Outputs):
43
+ final_results = StartNode.Outputs.bar
44
+
45
+ # AND a dataset for this workflow
46
+ inputs: List[Inputs] = [
47
+ Inputs(foo="first"),
48
+ Inputs(foo="second"),
49
+ ]
50
+
51
+ # WHEN we run the sandbox
52
+ runner = SandboxRunner(workflow=Workflow, inputs=inputs)
53
+ runner.run(**run_kwargs)
54
+
55
+ # THEN we see the logs
56
+ assert logs == [
57
+ "Just started Node: StartNode",
58
+ "Just finished Node: StartNode",
59
+ "Workflow fulfilled!",
60
+ "----------------------------------",
61
+ expected_last_log,
62
+ ]
@@ -1,6 +1,9 @@
1
1
  import dataclasses
2
2
  import inspect
3
- from typing import Any, Callable, Union, get_args, get_origin
3
+ from typing import Any, Callable, Optional, Union, get_args, get_origin
4
+
5
+ from pydantic import BaseModel
6
+ from pydantic_core import PydanticUndefined
4
7
 
5
8
  from vellum.client.types.function_definition import FunctionDefinition
6
9
 
@@ -16,7 +19,10 @@ type_map = {
16
19
  }
17
20
 
18
21
 
19
- def _compile_annotation(annotation: Any, defs: dict[str, Any]) -> dict:
22
+ def _compile_annotation(annotation: Optional[Any], defs: dict[str, Any]) -> dict:
23
+ if annotation is None:
24
+ return {"type": "null"}
25
+
20
26
  if get_origin(annotation) is Union:
21
27
  return {"anyOf": [_compile_annotation(a, defs) for a in get_args(annotation)]}
22
28
 
@@ -37,13 +43,44 @@ def _compile_annotation(annotation: Any, defs: dict[str, Any]) -> dict:
37
43
  if field.default is dataclasses.MISSING:
38
44
  required.append(field.name)
39
45
  else:
40
- properties[field.name]["default"] = field.default
46
+ properties[field.name]["default"] = _compile_default_value(field.default)
47
+ defs[annotation.__name__] = {"type": "object", "properties": properties, "required": required}
48
+ return {"$ref": f"#/$defs/{annotation.__name__}"}
49
+
50
+ if issubclass(annotation, BaseModel):
51
+ if annotation.__name__ not in defs:
52
+ properties = {}
53
+ required = []
54
+ for field_name, field in annotation.model_fields.items():
55
+ # Mypy is incorrect here, the `annotation` attribute is defined on `FieldInfo`
56
+ field_annotation = field.annotation # type: ignore[attr-defined]
57
+ properties[field_name] = _compile_annotation(field_annotation, defs)
58
+ if field.default is PydanticUndefined:
59
+ required.append(field_name)
60
+ else:
61
+ properties[field_name]["default"] = _compile_default_value(field.default)
41
62
  defs[annotation.__name__] = {"type": "object", "properties": properties, "required": required}
63
+
42
64
  return {"$ref": f"#/$defs/{annotation.__name__}"}
43
65
 
44
66
  return {"type": type_map[annotation]}
45
67
 
46
68
 
69
+ def _compile_default_value(default: Any) -> Any:
70
+ if dataclasses.is_dataclass(default):
71
+ return {
72
+ field.name: _compile_default_value(getattr(default, field.name)) for field in dataclasses.fields(default)
73
+ }
74
+
75
+ if isinstance(default, BaseModel):
76
+ return {
77
+ field_name: _compile_default_value(getattr(default, field_name))
78
+ for field_name in default.model_fields.keys()
79
+ }
80
+
81
+ return default
82
+
83
+
47
84
  def compile_function_definition(function: Callable) -> FunctionDefinition:
48
85
  """
49
86
  Converts a Python function into our Vellum-native FunctionDefinition type.
@@ -62,7 +99,7 @@ def compile_function_definition(function: Callable) -> FunctionDefinition:
62
99
  if param.default is inspect.Parameter.empty:
63
100
  required.append(param.name)
64
101
  else:
65
- properties[param.name]["default"] = param.default
102
+ properties[param.name]["default"] = _compile_default_value(param.default)
66
103
 
67
104
  parameters = {"type": "object", "properties": properties, "required": required}
68
105
  if defs:
@@ -1,6 +1,8 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import Dict, List, Optional, Union
3
3
 
4
+ from pydantic import BaseModel
5
+
4
6
  from vellum.client.types.function_definition import FunctionDefinition
5
7
  from vellum.workflows.utils.functions import compile_function_definition
6
8
 
@@ -169,3 +171,94 @@ def test_compile_function_definition__dataclasses():
169
171
  },
170
172
  },
171
173
  )
174
+
175
+
176
+ def test_compile_function_definition__pydantic():
177
+ # GIVEN a function with a pydantic model
178
+ class MyPydanticModel(BaseModel):
179
+ a: int
180
+ b: str
181
+
182
+ def my_function(c: MyPydanticModel):
183
+ pass
184
+
185
+ # WHEN compiling the function
186
+ compiled_function = compile_function_definition(my_function)
187
+
188
+ # THEN it should return the compiled function definition
189
+ assert compiled_function == FunctionDefinition(
190
+ name="my_function",
191
+ parameters={
192
+ "type": "object",
193
+ "properties": {"c": {"$ref": "#/$defs/MyPydanticModel"}},
194
+ "required": ["c"],
195
+ "$defs": {
196
+ "MyPydanticModel": {
197
+ "type": "object",
198
+ "properties": {"a": {"type": "integer"}, "b": {"type": "string"}},
199
+ "required": ["a", "b"],
200
+ }
201
+ },
202
+ },
203
+ )
204
+
205
+
206
+ def test_compile_function_definition__default_dataclass():
207
+ # GIVEN a function with a dataclass
208
+ @dataclass
209
+ class MyDataClass:
210
+ a: int
211
+ b: str
212
+
213
+ def my_function(c: MyDataClass = MyDataClass(a=1, b="hello")):
214
+ pass
215
+
216
+ # WHEN compiling the function
217
+ compiled_function = compile_function_definition(my_function)
218
+
219
+ # THEN it should return the compiled function definition
220
+ assert compiled_function == FunctionDefinition(
221
+ name="my_function",
222
+ parameters={
223
+ "type": "object",
224
+ "properties": {"c": {"$ref": "#/$defs/MyDataClass", "default": {"a": 1, "b": "hello"}}},
225
+ "required": [],
226
+ "$defs": {
227
+ "MyDataClass": {
228
+ "type": "object",
229
+ "properties": {"a": {"type": "integer"}, "b": {"type": "string"}},
230
+ "required": ["a", "b"],
231
+ }
232
+ },
233
+ },
234
+ )
235
+
236
+
237
+ def test_compile_function_definition__default_pydantic():
238
+ # GIVEN a function with a pydantic model as the default value
239
+ class MyPydanticModel(BaseModel):
240
+ a: int
241
+ b: str
242
+
243
+ def my_function(c: MyPydanticModel = MyPydanticModel(a=1, b="hello")):
244
+ pass
245
+
246
+ # WHEN compiling the function
247
+ compiled_function = compile_function_definition(my_function)
248
+
249
+ # THEN it should return the compiled function definition
250
+ assert compiled_function == FunctionDefinition(
251
+ name="my_function",
252
+ parameters={
253
+ "type": "object",
254
+ "properties": {"c": {"$ref": "#/$defs/MyPydanticModel", "default": {"a": 1, "b": "hello"}}},
255
+ "required": [],
256
+ "$defs": {
257
+ "MyPydanticModel": {
258
+ "type": "object",
259
+ "properties": {"a": {"type": "integer"}, "b": {"type": "string"}},
260
+ "required": ["a", "b"],
261
+ }
262
+ },
263
+ },
264
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.12.4
3
+ Version: 0.12.6
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -1,17 +1,17 @@
1
1
  vellum_cli/CONTRIBUTING.md,sha256=FtDC7BGxSeMnwCXAUssFsAIElXtmJE-O5Z7BpolcgvI,2935
2
2
  vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
3
- vellum_cli/__init__.py,sha256=XG6aC1NSPfBjNFzqiy9Lbk4DnELCKO8lpIZ3jeJR0r0,6990
3
+ vellum_cli/__init__.py,sha256=A9uo9OE7xQACNEtX4k0c-rxypDqS5V8kA8u4BNN0azM,7402
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
5
  vellum_cli/config.py,sha256=wJQnv3tCgu1BOugg0AOP94yQ-x1yAg8juX_QoFN9Y7w,5223
6
6
  vellum_cli/image_push.py,sha256=SJwhwWJsLjwGNezNVd_oCVpFMfPsAB3dfLWmriZZUtw,4419
7
7
  vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
8
- vellum_cli/pull.py,sha256=EkQirsTe0KCpJ8oHTE3S9_M2XHSrb2wrXNq4hTkMZng,5770
8
+ vellum_cli/pull.py,sha256=q68fr1o5H9l8Dvc8BTY1GARJYjAV1i6Fg-Lg4Oo4FDw,6155
9
9
  vellum_cli/push.py,sha256=gcYhIogeYejZIhNm5Cjp0VBECaXLmVQEvZjrPH0-TSU,5337
10
10
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  vellum_cli/tests/conftest.py,sha256=eFGwBxib3Nki830lIFintB0b6r4x8T_KMnmzhlTY5x0,1337
12
12
  vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
13
13
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
14
- vellum_cli/tests/test_pull.py,sha256=pGqpBkahC360UITDE8BlcJ7LPd9DyRPn4NrCNdkQmAQ,15907
14
+ vellum_cli/tests/test_pull.py,sha256=P2JFNHU1hE6iydYl7rW35h7c8_DSrLiAS7gNsFMy1JU,17829
15
15
  vellum_cli/tests/test_push.py,sha256=V2iGcskh2X3OHj2uV5Vx_BhmtyfmUkyx0lrp8DDOExc,5824
16
16
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -20,7 +20,7 @@ vellum_ee/workflows/display/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
20
20
  vellum_ee/workflows/display/base.py,sha256=3ZFUYRNKL24fBqXhKpa_Dq2W1a-a86J20dmJYA3H2eY,1755
21
21
  vellum_ee/workflows/display/nodes/__init__.py,sha256=5XOcZJXYUgaLS55QgRJzyQ_W1tpeprjnYAeYVezqoGw,160
22
22
  vellum_ee/workflows/display/nodes/base_node_display.py,sha256=23PLqcpMe_mYkYdug9PDb6Br7o64Thx9-IhcviKGvGo,6662
23
- vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=7F4zQnZQBOTp9turUUS41RaO40Z14m851WOLJHgmHPU,2234
23
+ vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=BxA-YVUJvB36NM-Q5DNCwckeqymwLKMp9DVCaTyrPbs,2253
24
24
  vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=vyKeJAevAXvEAEtWeTEdBZXD3eJQYW_DEXLKVZ5KmYc,1325
25
25
  vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=QqR3Ly0RNrXwOeLdW5nERDFt0gRPf76n1bPES6o5UN4,1093
@@ -28,8 +28,8 @@ vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6m
28
28
  vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
29
29
  vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nmPLj8vkbVCS46XQqmHq8Xj8Mr36wCK_vWf26A9KIkw,1505
30
30
  vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=4SSQGecKWHuoGy5YIGJeOZVHGKwTs_8Y-gf3GvsHb0M,8506
31
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=_sdLmmOa1ZqZQZXzw0GMoUpgnV1sdOfc_19LJCBS_Xc,4288
32
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=TP7ItMeEXbVbkGr5Qwjv8vl9V0Fi9QBE04d3kdJH4JE,13666
31
+ vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=XqizRn5bLwT8LMwgyvfbln8inhCxzTi1EkD22Fx-5-U,4222
32
+ vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=EtdqJfhYw03PuT2iyJ6mSAZK4RsQqDie_2AnJAtMelk,13625
33
33
  vellum_ee/workflows/display/nodes/vellum/error_node.py,sha256=ygTjSjYDI4DtkxADWub5rhBnRWItMKWF6fezBrgpOKA,1979
34
34
  vellum_ee/workflows/display/nodes/vellum/final_output_node.py,sha256=t5iJQVoRT5g-v2IiUb4kFYdvUVKch0zn27016pzDZoo,2761
35
35
  vellum_ee/workflows/display/nodes/vellum/guardrail_node.py,sha256=3TJvHX_Uuf_gr94VkYc_zmNH8I5p71ChIeoAbJZ3ddY,2158
@@ -39,13 +39,13 @@ vellum_ee/workflows/display/nodes/vellum/map_node.py,sha256=AqUlItgSZij12qRKguKV
39
39
  vellum_ee/workflows/display/nodes/vellum/merge_node.py,sha256=jzO63B9KiEAncnBqmz2ZTcxjmEHozMEe7WnfpcpsQYg,3195
40
40
  vellum_ee/workflows/display/nodes/vellum/note_node.py,sha256=9VpC3h0RYOxJuRbjDwidBYlLKakkmlEnDMBh2C7lHcY,1107
41
41
  vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py,sha256=gLRkizwyw21-Z12IyDbdOJpXayiZZd4HWd6qgZQg8sc,3106
42
- vellum_ee/workflows/display/nodes/vellum/search_node.py,sha256=I4_ENFWEhHUDYFUWF67-EyToCwmJynAXcbNpqw6Cpio,8681
42
+ vellum_ee/workflows/display/nodes/vellum/search_node.py,sha256=laNWcDt62VRM5hLfcOi-ouAyNhYiFRsD0PWbVGLwlLI,9035
43
43
  vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py,sha256=zOp4voBSgB3MR1R93wTOrsiiara_hxEAYFupLl_SvTA,2657
44
- vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=UNYxoE-89agE8ugK0aWg_uN61jPqlC2VSxWHk568sN4,3324
44
+ vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=IHumtFFZSanRizU3-0ATFgUnDuSFZMScZce8YTDJiGU,3373
45
45
  vellum_ee/workflows/display/nodes/vellum/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=aJfQnIvlrRHVKgQid_gg6VQKkJyPgFnzbvWt9_t0Vz0,3860
47
47
  vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=hB8dcGMGkuC95kk9hmZUgHsCLwEA37fHTFXj0JzbRjM,4692
48
- vellum_ee/workflows/display/nodes/vellum/utils.py,sha256=3QsKS5Kht2Nod4A18T0bb4filK9AkyK5oImVlViK3l8,4643
48
+ vellum_ee/workflows/display/nodes/vellum/utils.py,sha256=6mPg9QilJfLw9Sgk_h9LRSyKZpKgRu06mr3X1AmmDCA,4691
49
49
  vellum_ee/workflows/display/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=TEg3QbdE7rLbEhml9pMWmay--phsekGlfGVhTblxCGE,1727
51
51
  vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -76,7 +76,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
76
76
  vellum/client/__init__.py,sha256=o4m7iRZWEV8rP3GkdaztHAjNmjxjWERlarviFoHzuKI,110927
77
77
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
78
78
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
79
- vellum/client/core/client_wrapper.py,sha256=Txk5jgvpOyeOcWKUtrs-1p5HZ-zPxTysTZVwMYsX3-A,1868
79
+ vellum/client/core/client_wrapper.py,sha256=Djd_7GxqAd6z6spaztPvuwOWM8nCLYHOM1pxOFHfmrc,1868
80
80
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
81
81
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
82
82
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -130,7 +130,7 @@ vellum/client/resources/workflow_deployments/types/workflow_deployments_list_req
130
130
  vellum/client/resources/workflow_sandboxes/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
131
131
  vellum/client/resources/workflow_sandboxes/client.py,sha256=3wVQxkjrJ5bIS8fB5FpKXCP2dX38299ghWrJ8YmXxwQ,7435
132
132
  vellum/client/resources/workflows/__init__.py,sha256=Z4xi8Nxd9U4t35FQSepTt1p-ns0X1xtdNs168kUcuBk,153
133
- vellum/client/resources/workflows/client.py,sha256=5wqDiO1xYnYxNmbaEE1OTnUn3YXKpruYqF3taCjLRDI,10263
133
+ vellum/client/resources/workflows/client.py,sha256=1X9KKRIr-Jwxbkd2a43DYfukKRKgNL1IXkGb9VC15a0,11391
134
134
  vellum/client/resources/workflows/types/__init__.py,sha256=-uFca4ypncAOvfsg6sjD-5C9zWdA5qNvU6m675GphVg,177
135
135
  vellum/client/resources/workflows/types/workflows_pull_request_format.py,sha256=dOWE_jnDnniIJLoeseeCms23aklghyBkoPmBFzcqqZk,165
136
136
  vellum/client/resources/workspace_secrets/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
@@ -173,7 +173,7 @@ vellum/client/types/chat_history_vellum_value_request.py,sha256=HzAiysncG5unJ-tl
173
173
  vellum/client/types/chat_message.py,sha256=EOA8v5Ebx2KS9BtwBBGbuvSK-pn4xWYZiioHuuPWvzw,916
174
174
  vellum/client/types/chat_message_content.py,sha256=DQLB5rG40qLRLsmKWWo-XKa4rhk9TGQs_eFTFow2zEM,607
175
175
  vellum/client/types/chat_message_content_request.py,sha256=iFT_PmN6sUjeN1_fZXr2ePJEbSq_GZcClBvtu8SdVmQ,724
176
- vellum/client/types/chat_message_prompt_block.py,sha256=K0D42GPIgJYfYYigXU6ggCUbwHcB1LwMNDjXDybh3MQ,1351
176
+ vellum/client/types/chat_message_prompt_block.py,sha256=b6WmJqAc6r15XhHKsq3VmZBP-uoKV4jCSO-_xrDY5Qw,1351
177
177
  vellum/client/types/chat_message_request.py,sha256=r2EW1pfnvNYx2fo6mBqU5HQrUzp67WXuE5G-XK281E4,945
178
178
  vellum/client/types/chat_message_role.py,sha256=-i0Jrcbwf72MkMoaFTGyxRduvlN7f5Y9ULhCXR5KNdA,182
179
179
  vellum/client/types/code_execution_node_array_result.py,sha256=KCdbmjXjReO-hPPpBsSR17h_roDUpc4R-92cmIn59ck,952
@@ -271,7 +271,7 @@ vellum/client/types/function_call_request.py,sha256=eJBIN-wLkkkDUIwAy1nMeWHu3MZ5
271
271
  vellum/client/types/function_call_variable_value.py,sha256=VQKCiEtJsmIK3i7CtFV_2ZpxeX70rqpUViXIvAci8L0,702
272
272
  vellum/client/types/function_call_vellum_value.py,sha256=lLJb-S_-S_UXm6una1BMyCbqLpMhbbMcaVIYNO45h5o,759
273
273
  vellum/client/types/function_call_vellum_value_request.py,sha256=oUteuCfWcj7UJbSE_Vywmmva9kyTaeL9iv5WJHabDVs,788
274
- vellum/client/types/function_definition.py,sha256=D6uZTIndSeTT4O6FQjU7jVB6Tn1VWOqlAjCa92qX5cE,1127
274
+ vellum/client/types/function_definition.py,sha256=UzlrrBtdWe7RMStfc1UVdcnr4s8N4mOhsM306p6x_CE,1693
275
275
  vellum/client/types/generate_options_request.py,sha256=TUDqsH0tiPWDZH4T-p5gsvKvwVHEVZ_k6oI3qsjlsk4,782
276
276
  vellum/client/types/generate_request.py,sha256=gL6ywAJe6YCJ5oKbtYwL2H_TMdC_6PJZAI7-P3UOF3I,1286
277
277
  vellum/client/types/generate_response.py,sha256=QjbBGFGRE1tHcyDodM6Avzq0YHI4gsprkAWpdqZRrh4,1135
@@ -304,7 +304,7 @@ vellum/client/types/initiated_workflow_node_result_event.py,sha256=Nu1J4iQYsW2HH
304
304
  vellum/client/types/instructor_vectorizer_config.py,sha256=7udlosXv4CUWTW_Q9m0mz3VRi1FKSbBhDIOhtxRd0-U,731
305
305
  vellum/client/types/instructor_vectorizer_config_request.py,sha256=6LGFFQKntMfX7bdetUqEMVdr3KJHEps0oDp2bNmqWbM,738
306
306
  vellum/client/types/iteration_state_enum.py,sha256=83JSh842OJgQiLtNn1KMimy6RlEYRVH3mDmYWS6Ewzo,180
307
- vellum/client/types/jinja_prompt_block.py,sha256=Q2aIQh8a-i09kL1ISM-kzTq7TZoyaafDDlmCkUmLnVw,939
307
+ vellum/client/types/jinja_prompt_block.py,sha256=-wukO4TaDvGlrbkg9ZtDOPEfoLmaoX7830ktLGHRrvI,939
308
308
  vellum/client/types/json_input.py,sha256=ZUA2O9YueBCx0IMMdB8uYNSWJiSDZxMm5ogwbwCmz_g,761
309
309
  vellum/client/types/json_input_request.py,sha256=x5sA-VXxF4QH-98xRcIKPZhsMVbnJNUQofiUQqyfGk4,768
310
310
  vellum/client/types/json_variable_value.py,sha256=X7eBEWxuozfvIdqD5sIZ5L-L77Ou6IIsZaQVNXh5G2k,634
@@ -392,7 +392,7 @@ vellum/client/types/paginated_test_suite_test_case_list.py,sha256=9KrCCQKy0egMmV
392
392
  vellum/client/types/paginated_workflow_release_tag_read_list.py,sha256=dH24ESWyAMVtyHsBkxG8kJ9oORY04Wn3IN-7jvV7Lu4,818
393
393
  vellum/client/types/pdf_search_result_meta_source.py,sha256=EMVhqdN1bwE6Ujdx4VhlmKQtJvitN-57kY8oZPxh9dI,1126
394
394
  vellum/client/types/pdf_search_result_meta_source_request.py,sha256=nUhaD2Kw1paGC6O_ICVNu3R0e1SVgTshRTkGNgmcjXo,1133
395
- vellum/client/types/plain_text_prompt_block.py,sha256=GlzovdRbT6mhunMvIpF94_0apjh_0SZtkdlx5HFrE0k,930
395
+ vellum/client/types/plain_text_prompt_block.py,sha256=cqEN-B4mcvMw_9lBN7FQG8pk9b5LBJ9xpM6PTgkGiqs,930
396
396
  vellum/client/types/price.py,sha256=ewzXDBVLaleuXMVQ-gQ3G1Nl5J2OWOVEMEFfnQIpiTk,610
397
397
  vellum/client/types/processing_failure_reason_enum.py,sha256=R_KIW7TcQejhc-vLhtNf9SdkYADgoZCn4ch4_RRIvsI,195
398
398
  vellum/client/types/prompt_block.py,sha256=MIsxxmAmuT0thkkG12xm3THO5dlRLbFeMZBVTSvb788,493
@@ -424,7 +424,7 @@ vellum/client/types/rejected_workflow_node_result_event.py,sha256=o9AUc9hT60F8ck
424
424
  vellum/client/types/release_tag_source.py,sha256=YavosOXZ976yfXTNWRTZwh2HhRiYmSDk0bQCkl-jCoQ,158
425
425
  vellum/client/types/replace_test_suite_test_case_request.py,sha256=c1GT1RUCei1yWxyZy4Gv40PkXYisvK5OkzlqQ6WeBYA,1906
426
426
  vellum/client/types/rich_text_child_block.py,sha256=X_ACKFKSUx5SXT1cLp0Y5-7VrNxcGOggPm67Lk2442U,270
427
- vellum/client/types/rich_text_prompt_block.py,sha256=60Bg_1wuXAd5eBiO9xiN8NjMvYE67ZLTtAb1A3N-7oI,1036
427
+ vellum/client/types/rich_text_prompt_block.py,sha256=_Y2tRnSDtOUYHcq7zNfYbgqcLZab-Zd-Yh6UKdybVIY,1036
428
428
  vellum/client/types/sandbox_scenario.py,sha256=f4S0tDxmPYHIrD_BMjRL3XZGcGxlWy9apfI64hV7MBE,844
429
429
  vellum/client/types/scenario_input.py,sha256=caC8p5ZnuXhv2ZiHvzTvKL3Ebtr4NXP9Q8UcJEVB8-U,476
430
430
  vellum/client/types/scenario_input_chat_history_variable_value.py,sha256=ihgJktmZpz_12wx2yPtyrSrBjcBcSg9fby7h_eLoXgI,835
@@ -566,7 +566,7 @@ vellum/client/types/token_overlapping_window_chunking_request.py,sha256=IjCs9UDr
566
566
  vellum/client/types/unit_enum.py,sha256=BKWRVp2WfHtGK4D6TsolhNJHGHfExzrRHkFn8H8QkwQ,113
567
567
  vellum/client/types/upload_document_response.py,sha256=6_5Cm4yBPq5nD-rEql6GsmrAtSVVtNRczOL5YwsBVMI,649
568
568
  vellum/client/types/upsert_test_suite_test_case_request.py,sha256=iB38vx4mo4yNLV5XTeXMGR-PJLOQPloWQOAAi7PDpM0,2079
569
- vellum/client/types/variable_prompt_block.py,sha256=TnFQpfulqvbO-Ogs0u_IqQ6TPMMRC_Grz5uzd9-gMTs,946
569
+ vellum/client/types/variable_prompt_block.py,sha256=PZxTq_zu7wa5r2eTBbwCRL7hwDKaLaBT3URB1RR2WKw,946
570
570
  vellum/client/types/vellum_audio.py,sha256=oPm1bcxk7fTfWfHWOPSLvrZrRBjCyPDVDRMACPoWmMI,721
571
571
  vellum/client/types/vellum_audio_request.py,sha256=y9CZgQ1TteW0AHNk8GuAZLNVFa981rh7P9vyV8bfgys,728
572
572
  vellum/client/types/vellum_error.py,sha256=jCKfuCkDTiyFb1-QyP2cg0wReja6wMuooKPAjNhBA0M,643
@@ -1297,7 +1297,7 @@ vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org
1297
1297
  vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=EvylK1rGKpd4iiooEW9O5A9Q8DMTtBwETe_GtQT8M-E,2139
1298
1298
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py,sha256=Hl35IAoepRpE-j4cALaXVJIYTYOF3qszyVbxTj4kS1s,82
1299
1299
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/constants.py,sha256=fnjiRWLoRlC4Puo5oQcpZD5Hd-EesxsAo9l5tGAkpZQ,270
1300
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=H1AVDnitwIkwya12oV68Qj2tyb786pfRHHz5qxtubD4,5935
1300
+ vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=fypgmZHgaDtGqSBC8rjYiyryJ0H58LPt_CafLfAprO0,6341
1301
1301
  vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py,sha256=zdpNJoawB5PedsCCfgOGDDoWuif0jNtlV-K9sFL6cNQ,4968
1302
1302
  vellum/workflows/nodes/displayable/bases/search_node.py,sha256=pqiui8G6l_9FLE1HH4rCdFC73Bl7_AIBAmQQMjqe190,3570
1303
1303
  vellum/workflows/nodes/displayable/code_execution_node/__init__.py,sha256=0FLWMMktpzSnmBMizQglBpcPrP80fzVsoJwJgf822Cg,76
@@ -1316,7 +1316,7 @@ vellum/workflows/nodes/displayable/guardrail_node/node.py,sha256=7Ep7Ff7FtFry3Jw
1316
1316
  vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py,sha256=gSUOoEZLlrx35-tQhSAd3An8WDwBqyiQh-sIebLU9wU,74
1317
1317
  vellum/workflows/nodes/displayable/inline_prompt_node/node.py,sha256=dTnP1yH1P0NqMw3noxt9XwaDCpX8ZOhuvVYNAn_DdCQ,2119
1318
1318
  vellum/workflows/nodes/displayable/inline_prompt_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1319
- vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py,sha256=189Oo66QDYJS8vCcyLe9ErJBGpWZVmPePFHta8wzdeM,2615
1319
+ vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py,sha256=P1DUL0wIG-cyA5dqGv7242cFWJXysmombdujKrJtl7k,4669
1320
1320
  vellum/workflows/nodes/displayable/merge_node/__init__.py,sha256=J8IC08dSH7P76wKlNuxe1sn7toNGtSQdFirUbtPDEs0,60
1321
1321
  vellum/workflows/nodes/displayable/merge_node/node.py,sha256=ZyPvcTgfPOneOm5Dc2kUOoPkwNJqwRPZSj232akXynA,324
1322
1322
  vellum/workflows/nodes/displayable/note_node/__init__.py,sha256=KWA3P4fyYJ-fOTky8qNGlcOotQ-HeHJ9AjZt6mRQmCE,58
@@ -1328,7 +1328,7 @@ vellum/workflows/nodes/displayable/search_node/node.py,sha256=yhFWulbNmSQoDAwtTS
1328
1328
  vellum/workflows/nodes/displayable/subworkflow_deployment_node/__init__.py,sha256=9yYM6001YZeqI1VOk1QuEM_yrffk_EdsO7qaPzINKds,92
1329
1329
  vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py,sha256=pnbRCgdzWXrXhm5jDkDDASl5xu5w3DxskC34yJVmWUs,7147
1330
1330
  vellum/workflows/nodes/displayable/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1331
- vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py,sha256=lLnXKAUYtgvQ6MpT4GoTrqLtdlyDlUt1pPHrmu-Gf00,4705
1331
+ vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py,sha256=UI_RMmXn9qwB-StnFPvkDd9FctBQAg43wrfouqvPepk,4701
1332
1332
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=4CMwDtXwTaEvFfDpA6j2iLqc7S6IICSkvVZOobEpeps,6954
1333
1333
  vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=KqKJtJ0vuNoPuUPMdILmBTt4a2fBBxxun-nmOI7T8jo,2585
1334
1334
  vellum/workflows/nodes/utils.py,sha256=EZt7CzJmgQBR_GWFpZr8d-oaoti3tolTd2Cv9wm7dKo,1087
@@ -1353,6 +1353,7 @@ vellum/workflows/resolvers/__init__.py,sha256=eH6hTvZO4IciDaf_cf7aM2vs-DkBDyJPyc
1353
1353
  vellum/workflows/resolvers/base.py,sha256=WHra9LRtlTuB1jmuNqkfVE2JUgB61Cyntn8f0b0WZg4,411
1354
1354
  vellum/workflows/runner/__init__.py,sha256=i1iG5sAhtpdsrlvwgH6B-m49JsINkiWyPWs8vyT-bqM,72
1355
1355
  vellum/workflows/runner/runner.py,sha256=RXnLEmSJFbp0u4vKF7rvD2fscuYfcRYkspIJINnvFAI,27607
1356
+ vellum/workflows/sandbox.py,sha256=wNyOfd3gb6-O85EQcBIHNCnSYPH7Oufh2z4hQnR2HFU,2059
1356
1357
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
1357
1358
  vellum/workflows/state/base.py,sha256=jpSzF1OQd3-fqi6dMGlNsQl-7JnJxCdzWIigmX8Wz-I,14425
1358
1359
  vellum/workflows/state/context.py,sha256=oXiEdNsWJi1coRB85IreTgUeR6_CrWWBXndtLff9S7M,1272
@@ -1360,6 +1361,8 @@ vellum/workflows/state/encoder.py,sha256=kRrqwD0vFCiSRZR3rg8Sjkh8sDEerQQhlvmdSYQ
1360
1361
  vellum/workflows/state/store.py,sha256=VYGBQgN1bpd1as5eGiouV_7scg8QsRs4_1aqZAGIsRQ,793
1361
1362
  vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1362
1363
  vellum/workflows/state/tests/test_state.py,sha256=BQjcdREIK1MPuGhivRUgpynVJLftjEpH9RG3cRKxQEY,3310
1364
+ vellum/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1365
+ vellum/workflows/tests/test_sandbox.py,sha256=OMN15LuFUV58-XnAhtBiLcEjNt-Pj6bZgetDr8miCiw,1808
1363
1366
  vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
1364
1367
  vellum/workflows/types/core.py,sha256=D2NcSBwGgWj_mtXZqe3KnEQcb5qd5HzqAwnxwmlCfCw,899
1365
1368
  vellum/workflows/types/generics.py,sha256=ZkfoRhWs042i5IjA99v2wIhmh1u-Wieo3LzosgGWJVk,600
@@ -1368,10 +1371,10 @@ vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
1368
1371
  vellum/workflows/types/tests/test_utils.py,sha256=t6eqTSZqw2oVkIw6lP393qVmuyzjZZ62Ls1iELwZg_o,2434
1369
1372
  vellum/workflows/types/utils.py,sha256=Vk_itjV8YrfoT_Pm_x7QMvBdpCTr_XBTlszqZkQQJLw,5587
1370
1373
  vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1371
- vellum/workflows/utils/functions.py,sha256=udPTz1yFuSbHIQ6UeOUXro_hj4c8XdAb00_bH-Bf9N0,2529
1374
+ vellum/workflows/utils/functions.py,sha256=7A4BImhtY__qQpNrF5uPiwLfkj6PSUxYvF7ITigIkxY,4051
1372
1375
  vellum/workflows/utils/names.py,sha256=QLUqfJ1tmSEeUwBKTTiv_Qk3QGbInC2RSmlXfGXc8Wo,380
1373
1376
  vellum/workflows/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1374
- vellum/workflows/utils/tests/test_functions.py,sha256=68OdrS1Gf3incmRd4g3a7-nylqQbJdQjl7AyfVUJg5I,5223
1377
+ vellum/workflows/utils/tests/test_functions.py,sha256=ytdruS55aO2egsb5sAv8_9jf68L1cJoZu2uKV2iamrg,8083
1375
1378
  vellum/workflows/utils/tests/test_names.py,sha256=aOqpyvMsOEK_9mg_-yaNxQDW7QQfwqsYs37PseyLhxw,402
1376
1379
  vellum/workflows/utils/tests/test_uuids.py,sha256=i77ABQ0M3S-aFLzDXHJq_yr5FPkJEWCMBn1HJ3DObrE,437
1377
1380
  vellum/workflows/utils/tests/test_vellum_variables.py,sha256=0ISy1xjY7_rci0Mt_naK0xrmurE1REZLMCgPOCLFKRM,776
@@ -1381,8 +1384,8 @@ vellum/workflows/vellum_client.py,sha256=ODrq_TSl-drX2aezXegf7pizpWDVJuTXH-j6528
1381
1384
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1382
1385
  vellum/workflows/workflows/base.py,sha256=zpspOEdO5Ye_0ZvN-Wkzv9iQSiF1sD201ba8lhbnPbs,17086
1383
1386
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1384
- vellum_ai-0.12.4.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1385
- vellum_ai-0.12.4.dist-info/METADATA,sha256=NGdP3b6at6hPyXJWKWlyXKb0dSLEnYSUy3yIy1pVJIc,5128
1386
- vellum_ai-0.12.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1387
- vellum_ai-0.12.4.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1388
- vellum_ai-0.12.4.dist-info/RECORD,,
1387
+ vellum_ai-0.12.6.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1388
+ vellum_ai-0.12.6.dist-info/METADATA,sha256=C1VU1fn4OEzUUW4pv7NTqN6hMP4XCwxUZldj2hAtths,5128
1389
+ vellum_ai-0.12.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1390
+ vellum_ai-0.12.6.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1391
+ vellum_ai-0.12.6.dist-info/RECORD,,
vellum_cli/__init__.py CHANGED
@@ -125,10 +125,16 @@ Should only be used for debugging purposes.""",
125
125
  help="""Exclude the code definition of each Resource from the pull response. \
126
126
  Should only be used for debugging purposes.""",
127
127
  )
128
+ @click.option(
129
+ "--strict",
130
+ is_flag=True,
131
+ help="""Raises an error immediately if there are any issues with the pulling of the Resource.""",
132
+ )
128
133
  def pull(
129
134
  ctx: click.Context,
130
135
  include_json: Optional[bool],
131
136
  exclude_code: Optional[bool],
137
+ strict: Optional[bool],
132
138
  ) -> None:
133
139
  """Pull Resources from Vellum"""
134
140
 
@@ -136,6 +142,7 @@ def pull(
136
142
  pull_command(
137
143
  include_json=include_json,
138
144
  exclude_code=exclude_code,
145
+ strict=strict,
139
146
  )
140
147
 
141
148
 
@@ -159,12 +166,18 @@ Should only be used for debugging purposes.""",
159
166
  help="""Exclude the code definition of the Workflow from the pull response. \
160
167
  Should only be used for debugging purposes.""",
161
168
  )
169
+ @click.option(
170
+ "--strict",
171
+ is_flag=True,
172
+ help="""Raises an error immediately if there are any issues with the pulling of the Workflow.""",
173
+ )
162
174
  def workflows_pull(
163
175
  module: Optional[str],
164
176
  include_json: Optional[bool],
165
177
  workflow_sandbox_id: Optional[str],
166
178
  workflow_deployment: Optional[str],
167
179
  exclude_code: Optional[bool],
180
+ strict: Optional[bool],
168
181
  ) -> None:
169
182
  """
170
183
  Pull Workflows from Vellum. If a module is provided, only the Workflow for that module will be pulled.
@@ -177,6 +190,7 @@ def workflows_pull(
177
190
  workflow_sandbox_id=workflow_sandbox_id,
178
191
  workflow_deployment=workflow_deployment,
179
192
  exclude_code=exclude_code,
193
+ strict=strict,
180
194
  )
181
195
 
182
196
 
vellum_cli/pull.py CHANGED
@@ -13,6 +13,8 @@ from vellum.workflows.vellum_client import create_vellum_client
13
13
  from vellum_cli.config import VellumCliConfig, WorkflowConfig, load_vellum_cli_config
14
14
  from vellum_cli.logger import load_cli_logger
15
15
 
16
+ ERROR_LOG_FILE_NAME = "error.log"
17
+
16
18
 
17
19
  def _is_valid_uuid(val: Union[str, UUID, None]) -> bool:
18
20
  try:
@@ -81,6 +83,7 @@ def pull_command(
81
83
  workflow_deployment: Optional[str] = None,
82
84
  include_json: Optional[bool] = None,
83
85
  exclude_code: Optional[bool] = None,
86
+ strict: Optional[bool] = None,
84
87
  ) -> None:
85
88
  load_dotenv()
86
89
  logger = load_cli_logger()
@@ -109,6 +112,8 @@ def pull_command(
109
112
  query_parameters["include_json"] = include_json
110
113
  if exclude_code:
111
114
  query_parameters["exclude_code"] = exclude_code
115
+ if strict:
116
+ query_parameters["strict"] = strict
112
117
 
113
118
  response = client.workflows.pull(
114
119
  pk,
@@ -119,6 +124,7 @@ def pull_command(
119
124
  zip_buffer = io.BytesIO(zip_bytes)
120
125
 
121
126
  target_dir = os.path.join(os.getcwd(), *workflow_config.module.split("."))
127
+ error_content = ""
122
128
  with zipfile.ZipFile(zip_buffer) as zip_file:
123
129
  # Delete files in target_dir that aren't in the zip file
124
130
  if os.path.exists(target_dir):
@@ -146,8 +152,13 @@ def pull_command(
146
152
  target_file = os.path.join(target_dir, file_name)
147
153
  os.makedirs(os.path.dirname(target_file), exist_ok=True)
148
154
  with zip_file.open(file_name) as source, open(target_file, "w") as target:
155
+ content = source.read().decode("utf-8")
156
+ if file_name == ERROR_LOG_FILE_NAME:
157
+ error_content = content
158
+ continue
159
+
149
160
  logger.info(f"Writing to {target_file}...")
150
- target.write(source.read().decode("utf-8"))
161
+ target.write(content)
151
162
 
152
163
  if include_json:
153
164
  logger.warning(
@@ -158,4 +169,7 @@ Its schema should be considered unstable and subject to change at any time."""
158
169
  if save_lock_file:
159
170
  config.save()
160
171
 
161
- logger.info(f"Successfully pulled Workflow into {workflow_config.module}")
172
+ if error_content:
173
+ logger.error(error_content)
174
+ else:
175
+ logger.info(f"Successfully pulled Workflow into {workflow_config.module}")
@@ -441,3 +441,48 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
441
441
  "deployments": [],
442
442
  },
443
443
  ]
444
+
445
+
446
+ def test_pull__handle_error_log(vellum_client, mock_module):
447
+ # GIVEN a pyproject.toml with a workflow configured
448
+ temp_dir = mock_module.temp_dir
449
+ module = mock_module.module
450
+ workflow_sandbox_id = mock_module.workflow_sandbox_id
451
+
452
+ # AND the workflow pull API call returns a zip file with an error log
453
+ vellum_client.workflows.pull.return_value = iter(
454
+ [_zip_file_map({"workflow.py": "print('hello')", "error.log": "test error"})]
455
+ )
456
+
457
+ # WHEN the user runs the pull command with the new workflow sandbox id
458
+ runner = CliRunner()
459
+ result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-sandbox-id", workflow_sandbox_id])
460
+
461
+ # THEN the command returns successfully
462
+ assert result.exit_code == 0
463
+
464
+ # AND the error log file is not written to the module directory
465
+ assert not os.path.exists(os.path.join(temp_dir, *module.split("."), "error.log"))
466
+
467
+ # AND the error log is printed to the console
468
+ assert result.output.endswith("\x1b[31;20mtest error\x1b[0m\n")
469
+
470
+
471
+ def test_pull__strict__with_error(vellum_client, mock_module):
472
+ # GIVEN a pyproject.toml with a workflow configured
473
+ workflow_sandbox_id = mock_module.workflow_sandbox_id
474
+
475
+ # AND the workflow pull API call returns a zip file
476
+ vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
477
+
478
+ # WHEN the user runs the pull command with the new workflow sandbox id
479
+ runner = CliRunner()
480
+ result = runner.invoke(cli_main, ["workflows", "pull", "--strict", "--workflow-sandbox-id", workflow_sandbox_id])
481
+
482
+ # THEN the command returns successfully
483
+ assert result.exit_code == 0
484
+
485
+ # AND the pull api is called with strict=True
486
+ vellum_client.workflows.pull.assert_called_once()
487
+ call_args = vellum_client.workflows.pull.call_args.kwargs
488
+ assert call_args["request_options"]["additional_query_parameters"] == {"strict": True}
@@ -1,5 +1,5 @@
1
1
  from uuid import UUID
2
- from typing import ClassVar, Dict, Optional
2
+ from typing import ClassVar, Dict, Optional, Union
3
3
 
4
4
  from vellum.workflows.nodes.utils import get_unadorned_node
5
5
  from vellum.workflows.ports import Port
@@ -18,7 +18,7 @@ class BaseNodeVellumDisplay(BaseNodeDisplay[NodeType]):
18
18
  target_handle_id: ClassVar[Optional[UUID]] = None
19
19
 
20
20
  # Used to explicitly set the node input ids by name for a node
21
- node_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
21
+ node_input_ids_by_name: ClassVar[Dict[str, Union[UUID, str]]] = {}
22
22
 
23
23
  def _get_node_display_uuid(self, attribute: str) -> UUID:
24
24
  explicit_value = self._get_explicit_node_display_attr(attribute, UUID)
@@ -1,6 +1,6 @@
1
1
  import inspect
2
2
  from uuid import UUID
3
- from typing import ClassVar, Dict, Generic, Optional, TypeVar
3
+ from typing import ClassVar, Generic, Optional, TypeVar
4
4
 
5
5
  from vellum.workflows.nodes.displayable.code_execution_node import CodeExecutionNode
6
6
  from vellum.workflows.nodes.displayable.code_execution_node.utils import read_file_from_path
@@ -21,8 +21,6 @@ class BaseCodeExecutionNodeDisplay(BaseNodeVellumDisplay[_CodeExecutionNodeType]
21
21
  output_id: ClassVar[Optional[UUID]] = None
22
22
  log_output_id: ClassVar[Optional[UUID]] = None
23
23
 
24
- node_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
25
-
26
24
  def serialize(
27
25
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
28
26
  ) -> JsonObject:
@@ -40,17 +40,17 @@ _ConditionalNodeType = TypeVar("_ConditionalNodeType", bound=ConditionalNode)
40
40
 
41
41
  @dataclass
42
42
  class RuleIdMap:
43
- id: UUID
43
+ id: str
44
44
  lhs: Optional["RuleIdMap"]
45
45
  rhs: Optional["RuleIdMap"]
46
- field_node_input_id: Optional[UUID]
47
- value_node_input_id: Optional[UUID]
46
+ field_node_input_id: Optional[str]
47
+ value_node_input_id: Optional[str]
48
48
 
49
49
 
50
50
  @dataclass
51
51
  class ConditionId:
52
- id: UUID
53
- rule_group_id: Optional[UUID]
52
+ id: str
53
+ rule_group_id: Optional[str]
54
54
 
55
55
 
56
56
  class BaseConditionalNodeDisplay(BaseNodeVellumDisplay[_ConditionalNodeType], Generic[_ConditionalNodeType]):
@@ -112,7 +112,7 @@ but the defined conditions have length {len(condition_ids)}"""
112
112
  node_id, f"{current_id}.field", descriptor._expression, display_context, field_node_input_id
113
113
  )
114
114
  node_inputs.append(expression_node_input)
115
- field_node_input_id = UUID(expression_node_input.id)
115
+ field_node_input_id = expression_node_input.id
116
116
  operator = self._convert_descriptor_to_operator(descriptor)
117
117
 
118
118
  elif isinstance(descriptor, (BetweenExpression, NotBetweenExpression)):
@@ -128,8 +128,8 @@ but the defined conditions have length {len(condition_ids)}"""
128
128
  )
129
129
  node_inputs.extend([field_node_input, value_node_input])
130
130
  operator = self._convert_descriptor_to_operator(descriptor)
131
- field_node_input_id = UUID(field_node_input.id)
132
- value_node_input_id = UUID(value_node_input.id)
131
+ field_node_input_id = field_node_input.id
132
+ value_node_input_id = value_node_input.id
133
133
 
134
134
  else:
135
135
  lhs = descriptor._lhs # type: ignore[attr-defined]
@@ -145,19 +145,19 @@ but the defined conditions have length {len(condition_ids)}"""
145
145
  node_id, f"{current_id}.value", rhs, display_context, value_node_input_id
146
146
  )
147
147
  node_inputs.append(rhs_node_input)
148
- value_node_input_id = UUID(rhs_node_input.id)
148
+ value_node_input_id = rhs_node_input.id
149
149
 
150
150
  operator = self._convert_descriptor_to_operator(descriptor)
151
- field_node_input_id = UUID(lhs_node_input.id)
151
+ field_node_input_id = lhs_node_input.id
152
152
 
153
153
  return {
154
- "id": str(current_id),
154
+ "id": current_id,
155
155
  "rules": None,
156
156
  "combinator": None,
157
157
  "negated": False,
158
- "field_node_input_id": str(field_node_input_id),
158
+ "field_node_input_id": field_node_input_id,
159
159
  "operator": operator,
160
- "value_node_input_id": str(value_node_input_id),
160
+ "value_node_input_id": value_node_input_id,
161
161
  }
162
162
 
163
163
  conditions = []
@@ -263,7 +263,7 @@ but the defined conditions have length {len(condition_ids)}"""
263
263
 
264
264
  def get_nested_rule_details_by_path(
265
265
  self, rule_ids: List[RuleIdMap], path: List[int]
266
- ) -> Union[Tuple[UUID, Optional[UUID], Optional[UUID]], None]:
266
+ ) -> Union[Tuple[str, Optional[str], Optional[str]], None]:
267
267
  current_rule = rule_ids[path[0]]
268
268
 
269
269
  for step in path[1:]:
@@ -284,11 +284,11 @@ but the defined conditions have length {len(condition_ids)}"""
284
284
 
285
285
  return None
286
286
 
287
- def _generate_hash_for_rule_ids(self, node_id, rule_id) -> Tuple[UUID, UUID, UUID]:
287
+ def _generate_hash_for_rule_ids(self, node_id, rule_id) -> Tuple[str, str, str]:
288
288
  return (
289
- uuid4_from_hash(f"{node_id}|{rule_id}|current"),
290
- uuid4_from_hash(f"{node_id}|{rule_id}||field"),
291
- uuid4_from_hash(f"{node_id}|{rule_id}||value"),
289
+ str(uuid4_from_hash(f"{node_id}|{rule_id}|current")),
290
+ str(uuid4_from_hash(f"{node_id}|{rule_id}||field")),
291
+ str(uuid4_from_hash(f"{node_id}|{rule_id}||value")),
292
292
  )
293
293
 
294
294
  def _get_source_handle_ids(self) -> Dict[int, UUID]:
@@ -28,7 +28,9 @@ class VariableIdMap:
28
28
 
29
29
 
30
30
  class BaseSearchNodeDisplay(BaseNodeVellumDisplay[_SearchNodeType], Generic[_SearchNodeType]):
31
- input_variable_ids_by_logical_id: Optional[Dict[str, str]] = None
31
+ # A mapping between the id of the operand (e.g. "lhs_variable_id" or "rhs_variable_id") and the id of the node input
32
+ # that the operand is pointing to.
33
+ metadata_filter_input_id_by_operand_id: Dict[UUID, UUID] = {}
32
34
 
33
35
  def serialize(
34
36
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
@@ -149,18 +151,20 @@ class BaseSearchNodeDisplay(BaseNodeVellumDisplay[_SearchNodeType], Generic[_Sea
149
151
  variables,
150
152
  )
151
153
  elif isinstance(logical_expression, VellumValueLogicalConditionRequest):
152
- lhs_variable_id = str(logical_expression.lhs_variable.value)
153
- rhs_variable_id = str(logical_expression.rhs_variable.value)
154
- lhs_query_input_id = (
155
- self.input_variable_ids_by_logical_id[lhs_variable_id]
156
- if self.input_variable_ids_by_logical_id
157
- else str(uuid4_from_hash(f"{self.node_id}|{hash(tuple(path))}"))
158
- )
159
- rhs_query_input_id = (
160
- self.input_variable_ids_by_logical_id[rhs_variable_id]
161
- if self.input_variable_ids_by_logical_id
162
- else str(uuid4_from_hash(f"{self.node_id}|{hash(tuple(path))}"))
163
- )
154
+ lhs_variable_id = logical_expression.lhs_variable.value
155
+ if not isinstance(lhs_variable_id, str):
156
+ raise TypeError(f"Expected lhs_variable_id to be a string, got {type(lhs_variable_id)}")
157
+
158
+ rhs_variable_id = logical_expression.rhs_variable.value
159
+ if not isinstance(rhs_variable_id, str):
160
+ raise TypeError(f"Expected rhs_variable_id to be a string, got {type(rhs_variable_id)}")
161
+
162
+ lhs_query_input_id: UUID = self.metadata_filter_input_id_by_operand_id.get(
163
+ UUID(lhs_variable_id)
164
+ ) or uuid4_from_hash(f"{self.node_id}|{hash(tuple(path))}")
165
+ rhs_query_input_id: UUID = self.metadata_filter_input_id_by_operand_id.get(
166
+ UUID(rhs_variable_id)
167
+ ) or uuid4_from_hash(f"{self.node_id}|{hash(tuple(path))}")
164
168
 
165
169
  return (
166
170
  {
@@ -173,7 +177,7 @@ class BaseSearchNodeDisplay(BaseNodeVellumDisplay[_SearchNodeType], Generic[_Sea
173
177
  create_node_input(
174
178
  self.node_id,
175
179
  f"vellum-query-builder-variable-{lhs_variable_id}",
176
- lhs_query_input_id,
180
+ str(lhs_query_input_id),
177
181
  display_context,
178
182
  input_id=UUID(lhs_variable_id),
179
183
  pointer_type=InputVariablePointer,
@@ -181,7 +185,7 @@ class BaseSearchNodeDisplay(BaseNodeVellumDisplay[_SearchNodeType], Generic[_Sea
181
185
  create_node_input(
182
186
  self.node_id,
183
187
  f"vellum-query-builder-variable-{rhs_variable_id}",
184
- rhs_query_input_id,
188
+ str(rhs_query_input_id),
185
189
  display_context,
186
190
  input_id=UUID(rhs_variable_id),
187
191
  pointer_type=InputVariablePointer,
@@ -25,7 +25,7 @@ class BaseTemplatingNodeDisplay(BaseNodeVellumDisplay[_TemplatingNodeType], Gene
25
25
 
26
26
  template_input_id = self.template_input_id or next(
27
27
  (
28
- input_id
28
+ UUID(input_id) if isinstance(input_id, str) else input_id
29
29
  for input_name, input_id in self.node_input_ids_by_name.items()
30
30
  if input_name == TEMPLATE_INPUT_NAME
31
31
  ),
@@ -1,5 +1,5 @@
1
1
  from uuid import UUID
2
- from typing import Any, List, Optional, Type, cast
2
+ from typing import Any, List, Optional, Type, Union, cast
3
3
 
4
4
  from vellum.workflows.descriptors.base import BaseDescriptor
5
5
  from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
@@ -27,10 +27,10 @@ def create_node_input(
27
27
  input_name: str,
28
28
  value: Any,
29
29
  display_context: WorkflowDisplayContext,
30
- input_id: Optional[UUID],
30
+ input_id: Union[Optional[UUID], Optional[str]],
31
31
  pointer_type: Optional[Type[NodeInputValuePointerRule]] = ConstantValuePointer,
32
32
  ) -> NodeInput:
33
- input_id = input_id or uuid4_from_hash(f"{node_id}|{input_name}")
33
+ input_id = str(input_id) if input_id else str(uuid4_from_hash(f"{node_id}|{input_name}"))
34
34
  if (
35
35
  isinstance(value, OutputReference)
36
36
  and value.outputs_class._node_class
@@ -42,7 +42,7 @@ def create_node_input(
42
42
 
43
43
  rules = create_node_input_value_pointer_rules(value, display_context, pointer_type=pointer_type)
44
44
  return NodeInput(
45
- id=str(input_id),
45
+ id=input_id,
46
46
  key=input_name,
47
47
  value=NodeInputValuePointer(
48
48
  rules=rules,