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.
- vellum/__init__.py +10 -22
- vellum/client/__init__.py +8 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/core/pydantic_utilities.py +5 -0
- vellum/client/resources/__init__.py +4 -0
- vellum/client/resources/organizations/__init__.py +2 -0
- vellum/client/resources/organizations/client.py +116 -0
- vellum/client/resources/workflows/client.py +8 -0
- vellum/client/resources/workspaces/__init__.py +2 -0
- vellum/client/resources/workspaces/client.py +114 -0
- vellum/client/types/__init__.py +6 -22
- vellum/client/types/logical_operator.py +2 -0
- vellum/client/types/new_member_join_behavior_enum.py +8 -0
- vellum/client/types/{function_call_variable_value.py → organization_read.py} +6 -4
- vellum/client/types/workflow_execution_workflow_result_event.py +0 -2
- vellum/client/types/workflow_result_event.py +0 -2
- vellum/client/types/workflow_result_event_output_data_array.py +4 -4
- vellum/client/types/{string_variable_value.py → workspace_read.py} +12 -5
- vellum/{types/json_variable_value.py → resources/organizations/__init__.py} +1 -1
- vellum/resources/organizations/client.py +3 -0
- vellum/{types/image_variable_value.py → resources/workspaces/__init__.py} +1 -1
- vellum/{types/array_variable_value.py → resources/workspaces/client.py} +1 -1
- vellum/types/{array_variable_value_item.py → new_member_join_behavior_enum.py} +1 -1
- vellum/types/{audio_variable_value.py → organization_read.py} +1 -1
- vellum/types/{error_variable_value.py → workspace_read.py} +1 -1
- vellum/workflows/descriptors/base.py +1 -1
- vellum/workflows/descriptors/tests/test_utils.py +3 -0
- vellum/workflows/expressions/accessor.py +8 -2
- vellum/workflows/nodes/core/map_node/node.py +49 -24
- vellum/workflows/nodes/core/map_node/tests/test_node.py +4 -4
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +1 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -3
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +3 -0
- vellum/workflows/nodes/displayable/bases/search_node.py +37 -2
- vellum/workflows/nodes/displayable/bases/tests/__init__.py +0 -0
- vellum/workflows/nodes/displayable/bases/tests/test_utils.py +61 -0
- vellum/workflows/nodes/displayable/bases/types.py +42 -0
- vellum/workflows/nodes/displayable/bases/utils.py +112 -0
- vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +0 -1
- vellum/workflows/nodes/displayable/search_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/displayable/search_node/tests/test_node.py +164 -0
- vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +2 -3
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +0 -1
- vellum/workflows/runner/runner.py +37 -4
- vellum/workflows/types/tests/test_utils.py +5 -2
- vellum/workflows/types/utils.py +4 -0
- vellum/workflows/workflows/base.py +14 -32
- {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/METADATA +1 -1
- {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/RECORD +100 -97
- vellum_cli/__init__.py +10 -0
- vellum_cli/ping.py +28 -0
- vellum_cli/tests/test_ping.py +47 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +17 -10
- vellum_ee/workflows/display/nodes/vellum/api_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/base_node.py +110 -2
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py +5 -62
- vellum_ee/workflows/display/nodes/vellum/error_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/final_output_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +4 -0
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -1
- vellum_ee/workflows/display/nodes/vellum/merge_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/note_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +15 -10
- vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/utils.py +71 -1
- vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +2 -5
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py +18 -2
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +67 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +66 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +1015 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +37 -22
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +12 -56
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +43 -93
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +31 -151
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +8 -26
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +4 -15
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +9 -44
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +19 -101
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +19 -73
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +9 -44
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +9 -44
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +8 -6
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +11 -58
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +8 -11
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +7 -30
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +2 -11
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +9 -44
- vellum_ee/workflows/display/utils/vellum.py +4 -42
- vellum_ee/workflows/display/vellum.py +9 -43
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +7 -10
- vellum_ee/workflows/server/virtual_file_loader.py +3 -3
- vellum/client/types/array_variable_value.py +0 -27
- vellum/client/types/array_variable_value_item.py +0 -29
- vellum/client/types/audio_variable_value.py +0 -25
- vellum/client/types/chat_history_variable_value.py +0 -21
- vellum/client/types/error_variable_value.py +0 -21
- vellum/client/types/image_variable_value.py +0 -25
- vellum/client/types/json_variable_value.py +0 -20
- vellum/client/types/number_variable_value.py +0 -20
- vellum/client/types/search_results_variable_value.py +0 -21
- vellum/types/chat_history_variable_value.py +0 -3
- vellum/types/function_call_variable_value.py +0 -3
- vellum/types/number_variable_value.py +0 -3
- vellum/types/search_results_variable_value.py +0 -3
- vellum/types/string_variable_value.py +0 -3
- {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/LICENSE +0 -0
- {vellum_ai-0.12.17.dist-info → vellum_ai-0.13.1.dist-info}/WHEEL +0 -0
- {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
|
-
|
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.
|
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)
|
File without changes
|
@@ -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
|
File without changes
|
@@ -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=
|
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
|
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.
|
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
|
-
|
517
|
-
|
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):
|
vellum/workflows/types/utils.py
CHANGED
@@ -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],)
|