vellum-ai 0.12.17__py3-none-any.whl → 0.13.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. vellum/__init__.py +10 -22
  2. vellum/client/__init__.py +8 -0
  3. vellum/client/core/client_wrapper.py +1 -1
  4. vellum/client/core/pydantic_utilities.py +5 -0
  5. vellum/client/resources/__init__.py +4 -0
  6. vellum/client/resources/organizations/__init__.py +2 -0
  7. vellum/client/resources/organizations/client.py +116 -0
  8. vellum/client/resources/workflows/client.py +8 -0
  9. vellum/client/resources/workspaces/__init__.py +2 -0
  10. vellum/client/resources/workspaces/client.py +114 -0
  11. vellum/client/types/__init__.py +6 -22
  12. vellum/client/types/logical_operator.py +2 -0
  13. vellum/client/types/new_member_join_behavior_enum.py +8 -0
  14. vellum/client/types/{function_call_variable_value.py → organization_read.py} +6 -4
  15. vellum/client/types/workflow_execution_workflow_result_event.py +0 -2
  16. vellum/client/types/workflow_result_event.py +0 -2
  17. vellum/client/types/workflow_result_event_output_data_array.py +4 -4
  18. vellum/client/types/{string_variable_value.py → workspace_read.py} +12 -5
  19. vellum/{types/json_variable_value.py → resources/organizations/__init__.py} +1 -1
  20. vellum/resources/organizations/client.py +3 -0
  21. vellum/{types/image_variable_value.py → resources/workspaces/__init__.py} +1 -1
  22. vellum/{types/array_variable_value.py → resources/workspaces/client.py} +1 -1
  23. vellum/types/{array_variable_value_item.py → new_member_join_behavior_enum.py} +1 -1
  24. vellum/types/{audio_variable_value.py → organization_read.py} +1 -1
  25. vellum/types/{error_variable_value.py → workspace_read.py} +1 -1
  26. vellum/workflows/descriptors/base.py +1 -1
  27. vellum/workflows/descriptors/tests/test_utils.py +3 -0
  28. vellum/workflows/expressions/accessor.py +8 -2
  29. vellum/workflows/nodes/core/map_node/node.py +49 -24
  30. vellum/workflows/nodes/core/map_node/tests/test_node.py +4 -4
  31. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +1 -1
  32. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -3
  33. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +3 -0
  34. vellum/workflows/nodes/displayable/bases/search_node.py +37 -2
  35. vellum/workflows/nodes/displayable/bases/tests/__init__.py +0 -0
  36. vellum/workflows/nodes/displayable/bases/tests/test_utils.py +61 -0
  37. vellum/workflows/nodes/displayable/bases/types.py +42 -0
  38. vellum/workflows/nodes/displayable/bases/utils.py +112 -0
  39. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +0 -1
  40. vellum/workflows/nodes/displayable/search_node/tests/__init__.py +0 -0
  41. vellum/workflows/nodes/displayable/search_node/tests/test_node.py +164 -0
  42. vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +2 -3
  43. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +0 -1
  44. vellum/workflows/runner/runner.py +37 -4
  45. vellum/workflows/types/tests/test_utils.py +5 -2
  46. vellum/workflows/types/utils.py +4 -0
  47. vellum/workflows/workflows/base.py +14 -32
  48. {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/METADATA +1 -1
  49. {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/RECORD +100 -97
  50. vellum_cli/__init__.py +10 -0
  51. vellum_cli/ping.py +28 -0
  52. vellum_cli/tests/test_ping.py +47 -0
  53. vellum_ee/workflows/display/nodes/base_node_display.py +17 -10
  54. vellum_ee/workflows/display/nodes/vellum/api_node.py +1 -0
  55. vellum_ee/workflows/display/nodes/vellum/base_node.py +110 -2
  56. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -0
  57. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +5 -62
  58. vellum_ee/workflows/display/nodes/vellum/error_node.py +1 -0
  59. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +1 -0
  60. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +1 -0
  61. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +4 -0
  62. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +1 -0
  63. vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -1
  64. vellum_ee/workflows/display/nodes/vellum/merge_node.py +1 -0
  65. vellum_ee/workflows/display/nodes/vellum/note_node.py +1 -0
  66. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +15 -10
  67. vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -0
  68. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -0
  69. vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -0
  70. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +2 -2
  71. vellum_ee/workflows/display/nodes/vellum/utils.py +71 -1
  72. vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +2 -5
  73. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py +18 -2
  74. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +67 -0
  75. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +66 -0
  76. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +1015 -0
  77. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +37 -22
  78. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +12 -56
  79. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +43 -93
  80. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +31 -151
  81. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +8 -26
  82. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +4 -15
  83. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +9 -44
  84. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +19 -101
  85. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +19 -73
  86. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +9 -44
  87. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +9 -44
  88. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +8 -6
  89. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +11 -58
  90. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +8 -11
  91. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +7 -30
  92. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +2 -11
  93. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +9 -44
  94. vellum_ee/workflows/display/utils/vellum.py +4 -42
  95. vellum_ee/workflows/display/vellum.py +9 -43
  96. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +7 -10
  97. vellum_ee/workflows/server/virtual_file_loader.py +3 -3
  98. vellum/client/types/array_variable_value.py +0 -27
  99. vellum/client/types/array_variable_value_item.py +0 -29
  100. vellum/client/types/audio_variable_value.py +0 -25
  101. vellum/client/types/chat_history_variable_value.py +0 -21
  102. vellum/client/types/error_variable_value.py +0 -21
  103. vellum/client/types/image_variable_value.py +0 -25
  104. vellum/client/types/json_variable_value.py +0 -20
  105. vellum/client/types/number_variable_value.py +0 -20
  106. vellum/client/types/search_results_variable_value.py +0 -21
  107. vellum/types/chat_history_variable_value.py +0 -3
  108. vellum/types/function_call_variable_value.py +0 -3
  109. vellum/types/number_variable_value.py +0 -3
  110. vellum/types/search_results_variable_value.py +0 -3
  111. vellum/types/string_variable_value.py +0 -3
  112. {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/LICENSE +0 -0
  113. {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/WHEEL +0 -0
  114. {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/entry_points.txt +0 -0
@@ -74,6 +74,9 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
74
74
 
75
75
  compiled_inputs: List[PromptDeploymentInputRequest] = []
76
76
 
77
+ if not self.prompt_inputs:
78
+ return compiled_inputs
79
+
77
80
  for input_name, input_value in self.prompt_inputs.items():
78
81
  if isinstance(input_value, str):
79
82
  compiled_inputs.append(
@@ -15,6 +15,7 @@ from vellum.core import ApiError, RequestOptions
15
15
  from vellum.workflows.errors import WorkflowErrorCode
16
16
  from vellum.workflows.exceptions import NodeException
17
17
  from vellum.workflows.nodes.bases import BaseNode
18
+ from vellum.workflows.nodes.displayable.bases.types import SearchFilters
18
19
  from vellum.workflows.outputs import BaseOutputs
19
20
  from vellum.workflows.types.generics import StateType
20
21
 
@@ -33,7 +34,11 @@ class BaseSearchNode(BaseNode[StateType], Generic[StateType]):
33
34
  document_index: Union[UUID, str] - Either the UUID or name of the Vellum Document Index that you'd like to search
34
35
  against
35
36
  query: str - The query to search for
36
- options: Optional[SearchRequestOptionsRequest] = None - Runtime configuration for the search
37
+ limit: Optional[int] = None - The maximum number of results to return.
38
+ weights: Optional[SearchWeightsRequest] = None - The weights to use for the search. Must add up to 1.0.
39
+ result_merging: Optional[SearchResultMergingRequest] = None - The configuration for merging results.
40
+ filters: Optional[SearchFiltersRequest] = None - The filters to apply to the search.
41
+ options: Optional[SearchRequestOptionsRequest] = None - [DEPRECATED] Runtime configuration for the search
37
42
  request_options: Optional[RequestOptions] = None - The request options to use for the search
38
43
  """
39
44
 
@@ -43,11 +48,24 @@ class BaseSearchNode(BaseNode[StateType], Generic[StateType]):
43
48
  # The Document Index to Search against. Identified by either its UUID or its name.
44
49
  document_index: ClassVar[Union[UUID, str]]
45
50
 
51
+ # The maximum number of results to return.
52
+ limit: ClassVar[Optional[int]] = None
53
+
54
+ # The weights to use for the search. Must add up to 1.0.
55
+ weights: ClassVar[Optional[SearchWeightsRequest]] = None
56
+
57
+ # The configuration for merging results.
58
+ result_merging: ClassVar[Optional[SearchResultMergingRequest]] = None
59
+
60
+ # The filters to apply to the search.
61
+ filters: ClassVar[Optional[SearchFilters]] = None
62
+
46
63
  # Ideally we could reuse node descriptors to derive other node descriptor values. Two action items are
47
64
  # blocking us from doing so in this use case:
48
65
  # 1. Node Descriptor resolution during runtime - https://app.shortcut.com/vellum/story/4781
49
66
  # 2. Math operations between descriptors - https://app.shortcut.com/vellum/story/4782
50
67
  # search_weights = DEFAULT_SEARCH_WEIGHTS
68
+ # Deprecated: Use the top level `limit`, `weights`, `result_merging`, and `filters` attributes instead
51
69
  options = SearchRequestOptionsRequest(
52
70
  limit=DEFAULT_SEARCH_LIMIT,
53
71
  weights=SearchWeightsRequest(
@@ -77,7 +95,7 @@ class BaseSearchNode(BaseNode[StateType], Generic[StateType]):
77
95
  return self._context.vellum_client.search(
78
96
  query=self.query,
79
97
  document_index=str(self.document_index),
80
- options=self.options,
98
+ options=self._get_options_request(),
81
99
  )
82
100
  except NotFoundError:
83
101
  raise NodeException(
@@ -90,6 +108,23 @@ class BaseSearchNode(BaseNode[StateType], Generic[StateType]):
90
108
  code=WorkflowErrorCode.INTERNAL_ERROR,
91
109
  )
92
110
 
111
+ def _get_options_request(self) -> SearchRequestOptionsRequest:
112
+ return SearchRequestOptionsRequest(
113
+ limit=self.limit if self.limit is not None else self.options.limit,
114
+ weights=self.weights if self.weights is not None else self.options.weights,
115
+ result_merging=self.result_merging if self.result_merging is not None else self.options.result_merging,
116
+ filters=self._get_filters_request(),
117
+ )
118
+
119
+ def _get_filters_request(self) -> Optional[SearchFiltersRequest]:
120
+ if self.filters is None:
121
+ return self.options.filters
122
+
123
+ return SearchFiltersRequest(
124
+ external_ids=self.filters.external_ids,
125
+ metadata=self.filters.metadata.to_request() if self.filters.metadata is not None else None,
126
+ )
127
+
93
128
  def run(self) -> Outputs:
94
129
  response = self._perform_search()
95
130
  return self.Outputs(results=response.results)
@@ -0,0 +1,61 @@
1
+ import pytest
2
+ import enum
3
+
4
+ from vellum.client.types.chat_history_vellum_value import ChatHistoryVellumValue
5
+ from vellum.client.types.chat_message import ChatMessage
6
+ from vellum.client.types.json_vellum_value import JsonVellumValue
7
+ from vellum.client.types.number_vellum_value import NumberVellumValue
8
+ from vellum.client.types.search_result import SearchResult
9
+ from vellum.client.types.search_result_document import SearchResultDocument
10
+ from vellum.client.types.search_results_vellum_value import SearchResultsVellumValue
11
+ from vellum.client.types.string_vellum_value import StringVellumValue
12
+ from vellum.client.types.string_vellum_value_request import StringVellumValueRequest
13
+ from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value, primitive_to_vellum_value_request
14
+
15
+
16
+ class MockEnum(enum.Enum):
17
+ FOO = "foo"
18
+
19
+
20
+ @pytest.mark.parametrize(
21
+ ["value", "expected_output"],
22
+ [
23
+ ("hello", StringVellumValue(value="hello")),
24
+ (MockEnum.FOO, StringVellumValue(value="foo")),
25
+ (1, NumberVellumValue(value=1)),
26
+ (1.0, NumberVellumValue(value=1.0)),
27
+ (
28
+ [ChatMessage(role="USER", text="hello")],
29
+ ChatHistoryVellumValue(value=[ChatMessage(role="USER", text="hello")]),
30
+ ),
31
+ (
32
+ [
33
+ SearchResult(
34
+ text="Search query",
35
+ score="0.0",
36
+ keywords=["keywords"],
37
+ document=SearchResultDocument(label="label"),
38
+ )
39
+ ],
40
+ SearchResultsVellumValue(
41
+ value=[
42
+ SearchResult(
43
+ text="Search query",
44
+ score="0.0",
45
+ keywords=["keywords"],
46
+ document=SearchResultDocument(label="label"),
47
+ )
48
+ ]
49
+ ),
50
+ ),
51
+ (StringVellumValue(value="hello"), StringVellumValue(value="hello")),
52
+ (StringVellumValueRequest(value="hello"), StringVellumValueRequest(value="hello")),
53
+ ({"foo": "bar"}, JsonVellumValue(value={"foo": "bar"})),
54
+ ],
55
+ )
56
+ def test_primitive_to_vellum_value(value, expected_output):
57
+ assert primitive_to_vellum_value(value) == expected_output
58
+
59
+
60
+ def test_primitive_to_vellum_value_request():
61
+ assert primitive_to_vellum_value_request("hello") == StringVellumValueRequest(value="hello")
@@ -0,0 +1,42 @@
1
+ from typing import Any, List, Optional, Union
2
+
3
+ from vellum.client.core.pydantic_utilities import UniversalBaseModel
4
+ from vellum.client.types.condition_combinator import ConditionCombinator
5
+ from vellum.client.types.logical_operator import LogicalOperator
6
+ from vellum.client.types.vellum_value_logical_condition_group_request import VellumValueLogicalConditionGroupRequest
7
+ from vellum.client.types.vellum_value_logical_condition_request import VellumValueLogicalConditionRequest
8
+ from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value_request
9
+
10
+
11
+ class MetadataLogicalConditionGroup(UniversalBaseModel):
12
+ combinator: ConditionCombinator
13
+ negated: bool
14
+ conditions: List["MetadataLogicalExpression"]
15
+
16
+ def to_request(self) -> VellumValueLogicalConditionGroupRequest:
17
+ return VellumValueLogicalConditionGroupRequest(
18
+ combinator=self.combinator,
19
+ negated=self.negated,
20
+ conditions=[c.to_request() for c in self.conditions],
21
+ )
22
+
23
+
24
+ class MetadataLogicalCondition(UniversalBaseModel):
25
+ lhs_variable: Any
26
+ operator: LogicalOperator
27
+ rhs_variable: Any
28
+
29
+ def to_request(self) -> VellumValueLogicalConditionRequest:
30
+ return VellumValueLogicalConditionRequest(
31
+ lhs_variable=primitive_to_vellum_value_request(self.lhs_variable),
32
+ operator=self.operator,
33
+ rhs_variable=primitive_to_vellum_value_request(self.rhs_variable),
34
+ )
35
+
36
+
37
+ MetadataLogicalExpression = Union[MetadataLogicalConditionGroup, MetadataLogicalCondition]
38
+
39
+
40
+ class SearchFilters(UniversalBaseModel):
41
+ external_ids: Optional[List[str]] = None
42
+ metadata: Optional[MetadataLogicalConditionGroup] = None
@@ -0,0 +1,112 @@
1
+ import enum
2
+ import json
3
+ from typing import Any, List, Union, cast
4
+
5
+ from vellum.client.types.array_vellum_value import ArrayVellumValue
6
+ from vellum.client.types.array_vellum_value_request import ArrayVellumValueRequest
7
+ from vellum.client.types.audio_vellum_value import AudioVellumValue
8
+ from vellum.client.types.audio_vellum_value_request import AudioVellumValueRequest
9
+ from vellum.client.types.chat_history_vellum_value import ChatHistoryVellumValue
10
+ from vellum.client.types.chat_history_vellum_value_request import ChatHistoryVellumValueRequest
11
+ from vellum.client.types.chat_message import ChatMessage
12
+ from vellum.client.types.error_vellum_value import ErrorVellumValue
13
+ from vellum.client.types.error_vellum_value_request import ErrorVellumValueRequest
14
+ from vellum.client.types.function_call_vellum_value import FunctionCallVellumValue
15
+ from vellum.client.types.function_call_vellum_value_request import FunctionCallVellumValueRequest
16
+ from vellum.client.types.image_vellum_value import ImageVellumValue
17
+ from vellum.client.types.image_vellum_value_request import ImageVellumValueRequest
18
+ from vellum.client.types.json_vellum_value import JsonVellumValue
19
+ from vellum.client.types.json_vellum_value_request import JsonVellumValueRequest
20
+ from vellum.client.types.number_vellum_value import NumberVellumValue
21
+ from vellum.client.types.number_vellum_value_request import NumberVellumValueRequest
22
+ from vellum.client.types.search_result import SearchResult
23
+ from vellum.client.types.search_result_request import SearchResultRequest
24
+ from vellum.client.types.search_results_vellum_value import SearchResultsVellumValue
25
+ from vellum.client.types.search_results_vellum_value_request import SearchResultsVellumValueRequest
26
+ from vellum.client.types.string_vellum_value import StringVellumValue
27
+ from vellum.client.types.string_vellum_value_request import StringVellumValueRequest
28
+ from vellum.client.types.vellum_value import VellumValue
29
+ from vellum.client.types.vellum_value_request import VellumValueRequest
30
+
31
+ VELLUM_VALUE_REQUEST_TUPLE = (
32
+ StringVellumValueRequest,
33
+ NumberVellumValueRequest,
34
+ JsonVellumValueRequest,
35
+ ImageVellumValueRequest,
36
+ AudioVellumValueRequest,
37
+ FunctionCallVellumValueRequest,
38
+ ErrorVellumValueRequest,
39
+ ArrayVellumValueRequest,
40
+ ChatHistoryVellumValueRequest,
41
+ SearchResultsVellumValueRequest,
42
+ )
43
+
44
+
45
+ def primitive_to_vellum_value(value: Any) -> VellumValue:
46
+ """Converts a python primitive to a VellumValue"""
47
+
48
+ if isinstance(value, str):
49
+ return StringVellumValue(value=value)
50
+ elif isinstance(value, enum.Enum):
51
+ return StringVellumValue(value=value.value)
52
+ elif isinstance(value, (int, float)):
53
+ return NumberVellumValue(value=value)
54
+ elif isinstance(value, list) and (
55
+ all(isinstance(message, ChatMessage) for message in value)
56
+ or all(isinstance(message, ChatMessage) for message in value)
57
+ ):
58
+ chat_messages = cast(Union[List[ChatMessage], List[ChatMessage]], value)
59
+ return ChatHistoryVellumValue(value=chat_messages)
60
+ elif isinstance(value, list) and (
61
+ all(isinstance(search_result, SearchResultRequest) for search_result in value)
62
+ or all(isinstance(search_result, SearchResult) for search_result in value)
63
+ ):
64
+ search_results = cast(Union[List[SearchResultRequest], List[SearchResult]], value)
65
+ return SearchResultsVellumValue(value=search_results)
66
+ elif isinstance(
67
+ value,
68
+ (
69
+ StringVellumValue,
70
+ NumberVellumValue,
71
+ JsonVellumValue,
72
+ ImageVellumValue,
73
+ AudioVellumValue,
74
+ FunctionCallVellumValue,
75
+ ErrorVellumValue,
76
+ ArrayVellumValue,
77
+ ChatHistoryVellumValue,
78
+ SearchResultsVellumValue,
79
+ ),
80
+ ):
81
+ return value
82
+ elif isinstance(
83
+ value,
84
+ VELLUM_VALUE_REQUEST_TUPLE,
85
+ ):
86
+ # This type ignore is safe because consumers of this function won't care the difference between
87
+ # XVellumValue and XVellumValueRequest. Hopefully in the near future, neither will we
88
+ return value # type: ignore
89
+
90
+ try:
91
+ json_value = json.dumps(value)
92
+ except json.JSONDecodeError:
93
+ raise ValueError(f"Unsupported variable type: {value.__class__.__name__}")
94
+
95
+ return JsonVellumValue(value=json.loads(json_value))
96
+
97
+
98
+ def primitive_to_vellum_value_request(value: Any) -> VellumValueRequest:
99
+ vellum_value = primitive_to_vellum_value(value)
100
+ vellum_value_request_class = next(
101
+ (
102
+ vellum_value_request_class
103
+ for vellum_value_request_class in VELLUM_VALUE_REQUEST_TUPLE
104
+ if vellum_value_request_class.__name__.startswith(vellum_value.__class__.__name__)
105
+ ),
106
+ None,
107
+ )
108
+
109
+ if vellum_value_request_class is None:
110
+ raise ValueError(f"Unsupported variable type: {vellum_value.__class__.__name__}")
111
+
112
+ return vellum_value_request_class.model_validate(vellum_value.model_dump())
@@ -76,7 +76,6 @@ def test_inline_prompt_node__function_definitions(vellum_adhoc_prompt_client):
76
76
  class MyNode(InlinePromptNode):
77
77
  ml_model = "gpt-4o"
78
78
  functions = [my_function]
79
- prompt_inputs = {}
80
79
  blocks = []
81
80
 
82
81
  # AND a known response from invoking an inline prompt
@@ -0,0 +1,164 @@
1
+ from vellum import SearchResponse, SearchResult, SearchResultDocument
2
+ from vellum.client.types.search_filters_request import SearchFiltersRequest
3
+ from vellum.client.types.search_request_options_request import SearchRequestOptionsRequest
4
+ from vellum.client.types.search_result_merging_request import SearchResultMergingRequest
5
+ from vellum.client.types.search_weights_request import SearchWeightsRequest
6
+ from vellum.client.types.string_vellum_value_request import StringVellumValueRequest
7
+ from vellum.client.types.vellum_value_logical_condition_group_request import VellumValueLogicalConditionGroupRequest
8
+ from vellum.client.types.vellum_value_logical_condition_request import VellumValueLogicalConditionRequest
9
+ from vellum.workflows.nodes.displayable.bases.types import (
10
+ MetadataLogicalCondition,
11
+ MetadataLogicalConditionGroup,
12
+ SearchFilters,
13
+ )
14
+ from vellum.workflows.nodes.displayable.search_node.node import SearchNode
15
+
16
+
17
+ def test_run_workflow__happy_path(vellum_client):
18
+ """Confirm that we can successfully invoke a Workflow with the new option attributes"""
19
+
20
+ # GIVEN a workflow that's set up run a Search Node
21
+ class MySearchNode(SearchNode):
22
+ query = "Search query"
23
+ document_index = "document_index"
24
+ limit = 1
25
+ weights = SearchWeightsRequest(
26
+ semantic_similarity=0.8,
27
+ keywords=0.2,
28
+ )
29
+ result_merging = SearchResultMergingRequest(
30
+ enabled=True,
31
+ )
32
+ filters = SearchFilters(
33
+ external_ids=["external_id"],
34
+ metadata=MetadataLogicalConditionGroup(
35
+ combinator="AND",
36
+ negated=False,
37
+ conditions=[
38
+ MetadataLogicalCondition(
39
+ lhs_variable="TYPE",
40
+ operator="=",
41
+ rhs_variable="COMPANY",
42
+ )
43
+ ],
44
+ ),
45
+ )
46
+
47
+ # AND a Search request that will return a 200 ok resposne
48
+ search_response = SearchResponse(
49
+ results=[
50
+ SearchResult(
51
+ text="Search query", score="0.0", keywords=["keywords"], document=SearchResultDocument(label="label")
52
+ )
53
+ ]
54
+ )
55
+
56
+ vellum_client.search.return_value = search_response
57
+
58
+ # WHEN we run the workflow
59
+ outputs = MySearchNode().run()
60
+
61
+ # THEN the workflow should have completed successfully
62
+ assert outputs.text == "Search query"
63
+
64
+ # AND the options should be as expected
65
+ assert vellum_client.search.call_args.kwargs["options"] == SearchRequestOptionsRequest(
66
+ limit=1,
67
+ weights=SearchWeightsRequest(
68
+ semantic_similarity=0.8,
69
+ keywords=0.2,
70
+ ),
71
+ result_merging=SearchResultMergingRequest(
72
+ enabled=True,
73
+ ),
74
+ filters=SearchFiltersRequest(
75
+ external_ids=["external_id"],
76
+ metadata=VellumValueLogicalConditionGroupRequest(
77
+ combinator="AND",
78
+ negated=False,
79
+ conditions=[
80
+ VellumValueLogicalConditionRequest(
81
+ lhs_variable=StringVellumValueRequest(value="TYPE"),
82
+ operator="=",
83
+ rhs_variable=StringVellumValueRequest(value="COMPANY"),
84
+ )
85
+ ],
86
+ ),
87
+ ),
88
+ )
89
+
90
+
91
+ def test_run_workflow__happy_path__options_attribute(vellum_client):
92
+ """Confirm that we can successfully invoke a single Search node with the legacy options attribute"""
93
+
94
+ # GIVEN a workflow that's set up run a Search Node
95
+ class MySearchNode(SearchNode):
96
+ query = "Search query"
97
+ document_index = "document_index"
98
+ options = SearchRequestOptionsRequest(
99
+ limit=1,
100
+ weights=SearchWeightsRequest(
101
+ semantic_similarity=0.8,
102
+ keywords=0.2,
103
+ ),
104
+ result_merging=SearchResultMergingRequest(
105
+ enabled=True,
106
+ ),
107
+ filters=SearchFiltersRequest(
108
+ external_ids=["external_id"],
109
+ metadata=VellumValueLogicalConditionGroupRequest(
110
+ combinator="AND",
111
+ negated=False,
112
+ conditions=[
113
+ VellumValueLogicalConditionRequest(
114
+ lhs_variable=StringVellumValueRequest(value="TYPE"),
115
+ operator="=",
116
+ rhs_variable=StringVellumValueRequest(value="COMPANY"),
117
+ )
118
+ ],
119
+ ),
120
+ ),
121
+ )
122
+
123
+ # AND a Search request that will return a 200 ok resposne
124
+ search_response = SearchResponse(
125
+ results=[
126
+ SearchResult(
127
+ text="Search query", score="0.0", keywords=["keywords"], document=SearchResultDocument(label="label")
128
+ )
129
+ ]
130
+ )
131
+
132
+ vellum_client.search.return_value = search_response
133
+
134
+ # WHEN we run the workflow
135
+ outputs = MySearchNode().run()
136
+
137
+ # THEN the workflow should have completed successfully
138
+ assert outputs.text == "Search query"
139
+
140
+ # AND the options should be as expected
141
+ assert vellum_client.search.call_args.kwargs["options"] == SearchRequestOptionsRequest(
142
+ limit=1,
143
+ weights=SearchWeightsRequest(
144
+ semantic_similarity=0.8,
145
+ keywords=0.2,
146
+ ),
147
+ result_merging=SearchResultMergingRequest(
148
+ enabled=True,
149
+ ),
150
+ filters=SearchFiltersRequest(
151
+ external_ids=["external_id"],
152
+ metadata=VellumValueLogicalConditionGroupRequest(
153
+ combinator="AND",
154
+ negated=False,
155
+ conditions=[
156
+ VellumValueLogicalConditionRequest(
157
+ lhs_variable=StringVellumValueRequest(value="TYPE"),
158
+ operator="=",
159
+ rhs_variable=StringVellumValueRequest(value="COMPANY"),
160
+ )
161
+ ],
162
+ ),
163
+ ),
164
+ )
@@ -1,3 +1,4 @@
1
+ from unittest import mock
1
2
  from uuid import uuid4
2
3
  from typing import Any, Iterator, List
3
4
 
@@ -33,7 +34,6 @@ def test_inline_text_prompt_node__basic(vellum_adhoc_prompt_client):
33
34
 
34
35
  class MyInlinePromptNode(InlinePromptNode):
35
36
  ml_model = "gpt-4o"
36
- prompt_inputs = {}
37
37
  blocks = []
38
38
 
39
39
  # AND a known response from invoking an inline prompt
@@ -90,7 +90,7 @@ def test_inline_text_prompt_node__basic(vellum_adhoc_prompt_client):
90
90
  logit_bias=None,
91
91
  custom_parameters=None,
92
92
  ),
93
- request_options=None,
93
+ request_options=mock.ANY,
94
94
  )
95
95
 
96
96
 
@@ -107,7 +107,6 @@ def test_inline_text_prompt_node__catch_provider_error(vellum_adhoc_prompt_clien
107
107
  @TryNode.wrap(on_error_code=WorkflowErrorCode.PROVIDER_ERROR)
108
108
  class MyInlinePromptNode(InlinePromptNode):
109
109
  ml_model = "gpt-4o"
110
- prompt_inputs = {}
111
110
  blocks = []
112
111
 
113
112
  # AND a known response from invoking an inline prompt that fails
@@ -27,7 +27,6 @@ def test_text_prompt_deployment_node__basic(vellum_client):
27
27
 
28
28
  class MyPromptDeploymentNode(PromptDeploymentNode):
29
29
  deployment = "my-deployment"
30
- prompt_inputs = {}
31
30
 
32
31
  # AND a known response from invoking a deployed prompt
33
32
  expected_outputs: List[PromptOutput] = [
@@ -4,7 +4,21 @@ import logging
4
4
  from queue import Empty, Queue
5
5
  from threading import Event as ThreadingEvent, Thread
6
6
  from uuid import UUID
7
- from typing import TYPE_CHECKING, Any, Dict, Generic, Iterable, Iterator, List, Optional, Sequence, Set, Type, Union
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ Dict,
11
+ Generic,
12
+ Iterable,
13
+ Iterator,
14
+ List,
15
+ Optional,
16
+ Sequence,
17
+ Set,
18
+ Tuple,
19
+ Type,
20
+ Union,
21
+ )
8
22
 
9
23
  from vellum.workflows.constants import UNDEF
10
24
  from vellum.workflows.context import execution_context, get_parent_context
@@ -76,6 +90,7 @@ class WorkflowRunner(Generic[StateType]):
76
90
  cancel_signal: Optional[ThreadingEvent] = None,
77
91
  node_output_mocks: Optional[List[BaseOutputs]] = None,
78
92
  parent_context: Optional[ParentContext] = None,
93
+ max_concurrency: Optional[int] = None,
79
94
  ):
80
95
  if state and external_inputs:
81
96
  raise ValueError("Can only run a Workflow providing one of state or external inputs, not both")
@@ -120,6 +135,9 @@ class WorkflowRunner(Generic[StateType]):
120
135
  # This queue is responsible for sending events from the inner worker threads to WorkflowRunner
121
136
  self._workflow_event_inner_queue: Queue[WorkflowEvent] = Queue()
122
137
 
138
+ self._max_concurrency = max_concurrency
139
+ self._concurrency_queue: Queue[Tuple[StateType, Type[BaseNode], Optional[Edge]]] = Queue()
140
+
123
141
  # This queue is responsible for sending events from WorkflowRunner to the background thread
124
142
  # for user defined emitters
125
143
  self._background_thread_queue: Queue[BackgroundThreadItem] = Queue()
@@ -350,7 +368,19 @@ class WorkflowRunner(Generic[StateType]):
350
368
  else:
351
369
  next_state = state
352
370
 
353
- self._run_node_if_ready(next_state, edge.to_node, edge)
371
+ if self._max_concurrency:
372
+ self._concurrency_queue.put((next_state, edge.to_node, edge))
373
+ else:
374
+ self._run_node_if_ready(next_state, edge.to_node, edge)
375
+
376
+ if self._max_concurrency:
377
+ num_nodes_to_run = self._max_concurrency - len(self._active_nodes_by_execution_id)
378
+ for _ in range(num_nodes_to_run):
379
+ if self._concurrency_queue.empty():
380
+ break
381
+
382
+ next_state, node_class, invoked_edge = self._concurrency_queue.get()
383
+ self._run_node_if_ready(next_state, node_class, invoked_edge)
354
384
 
355
385
  def _run_node_if_ready(
356
386
  self,
@@ -513,8 +543,11 @@ class WorkflowRunner(Generic[StateType]):
513
543
  )
514
544
  for node_cls in self._entrypoints:
515
545
  try:
516
- with execution_context(parent_context=current_parent):
517
- self._run_node_if_ready(self._initial_state, node_cls)
546
+ if not self._max_concurrency or len(self._active_nodes_by_execution_id) < self._max_concurrency:
547
+ with execution_context(parent_context=current_parent):
548
+ self._run_node_if_ready(self._initial_state, node_cls)
549
+ else:
550
+ self._concurrency_queue.put((self._initial_state, node_cls, None))
518
551
  except NodeException as e:
519
552
  self._workflow_event_outer_queue.put(self._reject_workflow_event(e.error))
520
553
  return
@@ -21,6 +21,7 @@ class ExampleClass:
21
21
  zeta: ClassVar[str]
22
22
  eta: List[str]
23
23
  kappa: Any
24
+ mu: list[str]
24
25
 
25
26
 
26
27
  T = TypeVar("T")
@@ -56,6 +57,7 @@ class ExampleNode(BaseNode):
56
57
  (ExampleInheritedClass, "beta", (int,)),
57
58
  (ExampleNode.Outputs, "iota", (str,)),
58
59
  (ExampleClass, "kappa", (Any,)),
60
+ (ExampleClass, "mu", (list[str],)),
59
61
  ],
60
62
  ids=[
61
63
  "str",
@@ -71,6 +73,7 @@ class ExampleNode(BaseNode):
71
73
  "inherited_parent_class_var",
72
74
  "try_node_output",
73
75
  "any",
76
+ "list_str_generic",
74
77
  ],
75
78
  )
76
79
  def test_infer_types(cls, attr_name, expected_type):
@@ -80,9 +83,9 @@ def test_infer_types(cls, attr_name, expected_type):
80
83
  @pytest.mark.parametrize(
81
84
  "cls, expected_attr_names",
82
85
  [
83
- (ExampleClass, {"alpha", "beta", "gamma", "epsilon", "zeta", "eta", "kappa"}),
86
+ (ExampleClass, {"alpha", "beta", "gamma", "epsilon", "zeta", "eta", "kappa", "mu"}),
84
87
  (ExampleGenericClass, {"delta"}),
85
- (ExampleInheritedClass, {"alpha", "beta", "gamma", "epsilon", "zeta", "eta", "theta", "kappa"}),
88
+ (ExampleInheritedClass, {"alpha", "beta", "gamma", "epsilon", "zeta", "eta", "theta", "kappa", "mu"}),
86
89
  ],
87
90
  )
88
91
  def test_class_attr_names(cls, expected_attr_names):
@@ -1,6 +1,7 @@
1
1
  from copy import deepcopy
2
2
  from datetime import datetime
3
3
  import importlib
4
+ from types import GenericAlias
4
5
  from typing import (
5
6
  Any,
6
7
  ClassVar,
@@ -77,6 +78,9 @@ def infer_types(object_: Type, attr_name: str, localns: Optional[Dict[str, Any]]
77
78
  return (type_hint,)
78
79
  if isinstance(type_hint, SpecialGenericAlias):
79
80
  return (type_hint,)
81
+ if isinstance(type_hint, GenericAlias):
82
+ # In future versions of python, list[str] will be a `GenericAlias`
83
+ return (cast(Type, type_hint),)
80
84
  if isinstance(type_hint, TypeVar):
81
85
  if type_hint in type_var_mapping:
82
86
  return (type_var_mapping[type_hint],)