vellum-ai 0.12.7__py3-none-any.whl → 0.12.9__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- vellum/client/core/client_wrapper.py +1 -1
- vellum/evaluations/resources.py +2 -2
- vellum/prompts/__init__.py +0 -0
- vellum/prompts/blocks/__init__.py +0 -0
- vellum/prompts/blocks/compilation.py +190 -0
- vellum/prompts/blocks/exceptions.py +2 -0
- vellum/prompts/blocks/tests/__init__.py +0 -0
- vellum/prompts/blocks/tests/test_compilation.py +110 -0
- vellum/prompts/blocks/types.py +36 -0
- vellum/utils/__init__.py +0 -0
- vellum/utils/templating/__init__.py +0 -0
- vellum/utils/templating/constants.py +28 -0
- vellum/{workflows/nodes/core/templating_node → utils/templating}/render.py +1 -1
- vellum/workflows/nodes/bases/__init__.py +0 -2
- vellum/workflows/nodes/bases/base.py +2 -6
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +13 -7
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +41 -0
- vellum/workflows/nodes/core/map_node/node.py +1 -1
- vellum/workflows/nodes/core/templating_node/node.py +12 -31
- vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +66 -0
- vellum/workflows/nodes/core/try_node/node.py +1 -3
- vellum/workflows/nodes/core/try_node/tests/test_node.py +1 -1
- vellum/workflows/nodes/displayable/bases/api_node/node.py +2 -2
- vellum/workflows/nodes/displayable/bases/search_node.py +5 -2
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +4 -2
- vellum/workflows/nodes/experimental/README.md +6 -0
- vellum/workflows/nodes/experimental/__init__.py +0 -0
- vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py +5 -0
- vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py +260 -0
- vellum/workflows/sandbox.py +7 -5
- vellum/workflows/state/context.py +5 -4
- vellum/workflows/tests/test_sandbox.py +2 -2
- vellum/workflows/utils/tests/test_vellum_variables.py +3 -0
- vellum/workflows/utils/vellum_variables.py +5 -4
- vellum/workflows/workflows/base.py +5 -2
- {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/METADATA +2 -1
- {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/RECORD +48 -33
- vellum_cli/tests/test_push.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +6 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +207 -0
- vellum/workflows/nodes/bases/base_subworkflow_node/__init__.py +0 -5
- vellum/workflows/nodes/bases/base_subworkflow_node/node.py +0 -10
- /vellum/{workflows/nodes/core/templating_node → utils/templating}/custom_filters.py +0 -0
- /vellum/{workflows/nodes/core/templating_node → utils/templating}/exceptions.py +0 -0
- /vellum/{evaluations/utils → utils}/typing.py +0 -0
- /vellum/{evaluations/utils → utils}/uuid.py +0 -0
- {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/LICENSE +0 -0
- {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/WHEEL +0 -0
- {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/entry_points.txt +0 -0
@@ -2,6 +2,8 @@ import json
|
|
2
2
|
|
3
3
|
from vellum.workflows.nodes.bases.base import BaseNode
|
4
4
|
from vellum.workflows.nodes.core.templating_node.node import TemplatingNode
|
5
|
+
from vellum.workflows.state import BaseState
|
6
|
+
from vellum.workflows.types.core import Json
|
5
7
|
|
6
8
|
|
7
9
|
def test_templating_node__dict_output():
|
@@ -22,6 +24,70 @@ def test_templating_node__dict_output():
|
|
22
24
|
assert json.loads(outputs.result) == {"key": "value"}
|
23
25
|
|
24
26
|
|
27
|
+
def test_templating_node__int_output():
|
28
|
+
# GIVEN a templating node that outputs an integer
|
29
|
+
class IntTemplateNode(TemplatingNode[BaseState, int]):
|
30
|
+
template = "{{ data }}"
|
31
|
+
inputs = {
|
32
|
+
"data": 42,
|
33
|
+
}
|
34
|
+
|
35
|
+
# WHEN the node is run
|
36
|
+
node = IntTemplateNode()
|
37
|
+
outputs = node.run()
|
38
|
+
|
39
|
+
# THEN the output is the expected integer
|
40
|
+
assert outputs.result == 42
|
41
|
+
|
42
|
+
|
43
|
+
def test_templating_node__float_output():
|
44
|
+
# GIVEN a templating node that outputs a float
|
45
|
+
class FloatTemplateNode(TemplatingNode[BaseState, float]):
|
46
|
+
template = "{{ data }}"
|
47
|
+
inputs = {
|
48
|
+
"data": 42.5,
|
49
|
+
}
|
50
|
+
|
51
|
+
# WHEN the node is run
|
52
|
+
node = FloatTemplateNode()
|
53
|
+
outputs = node.run()
|
54
|
+
|
55
|
+
# THEN the output is the expected float
|
56
|
+
assert outputs.result == 42.5
|
57
|
+
|
58
|
+
|
59
|
+
def test_templating_node__bool_output():
|
60
|
+
# GIVEN a templating node that outputs a bool
|
61
|
+
class BoolTemplateNode(TemplatingNode[BaseState, bool]):
|
62
|
+
template = "{{ data }}"
|
63
|
+
inputs = {
|
64
|
+
"data": True,
|
65
|
+
}
|
66
|
+
|
67
|
+
# WHEN the node is run
|
68
|
+
node = BoolTemplateNode()
|
69
|
+
outputs = node.run()
|
70
|
+
|
71
|
+
# THEN the output is the expected bool
|
72
|
+
assert outputs.result is True
|
73
|
+
|
74
|
+
|
75
|
+
def test_templating_node__json_output():
|
76
|
+
# GIVEN a templating node that outputs JSON
|
77
|
+
class JSONTemplateNode(TemplatingNode[BaseState, Json]):
|
78
|
+
template = "{{ data }}"
|
79
|
+
inputs = {
|
80
|
+
"data": {"key": "value"},
|
81
|
+
}
|
82
|
+
|
83
|
+
# WHEN the node is run
|
84
|
+
node = JSONTemplateNode()
|
85
|
+
outputs = node.run()
|
86
|
+
|
87
|
+
# THEN the output is the expected JSON
|
88
|
+
assert outputs.result == {"key": "value"}
|
89
|
+
|
90
|
+
|
25
91
|
def test_templating_node__execution_count_reference():
|
26
92
|
# GIVEN a random node
|
27
93
|
class OtherNode(BaseNode):
|
@@ -73,9 +73,7 @@ class TryNode(BaseNode[StateType], Generic[StateType], metaclass=_TryNodeMeta):
|
|
73
73
|
def run(self) -> Iterator[BaseOutput]:
|
74
74
|
subworkflow = self.subworkflow(
|
75
75
|
parent_state=self.state,
|
76
|
-
context=WorkflowContext(
|
77
|
-
_vellum_client=self._context._vellum_client,
|
78
|
-
),
|
76
|
+
context=WorkflowContext(vellum_client=self._context.vellum_client),
|
79
77
|
)
|
80
78
|
subworkflow_stream = subworkflow.stream(
|
81
79
|
event_filter=all_workflow_event_filter,
|
@@ -103,7 +103,7 @@ def test_try_node__use_parent_execution_context():
|
|
103
103
|
# WHEN the node is run with a custom vellum client
|
104
104
|
node = TestNode(
|
105
105
|
context=WorkflowContext(
|
106
|
-
|
106
|
+
vellum_client=Vellum(api_key="test-key"),
|
107
107
|
)
|
108
108
|
)
|
109
109
|
outputs = list(node.run())
|
@@ -26,11 +26,11 @@ class BaseAPINode(BaseNode, Generic[StateType]):
|
|
26
26
|
url: str
|
27
27
|
method: APIRequestMethod
|
28
28
|
data: Optional[str] = None
|
29
|
-
json: Optional[
|
29
|
+
json: Optional[Json] = None
|
30
30
|
headers: Optional[Dict[str, Union[str, VellumSecret]]] = None
|
31
31
|
|
32
32
|
class Outputs(BaseOutputs):
|
33
|
-
json: Optional[
|
33
|
+
json: Optional[Json]
|
34
34
|
headers: Dict[str, str]
|
35
35
|
status_code: int
|
36
36
|
text: str
|
@@ -12,6 +12,7 @@ from vellum import (
|
|
12
12
|
SearchWeightsRequest,
|
13
13
|
)
|
14
14
|
from vellum.core import ApiError, RequestOptions
|
15
|
+
from vellum.utils.uuid import is_valid_uuid
|
15
16
|
from vellum.workflows.errors import WorkflowErrorCode
|
16
17
|
from vellum.workflows.exceptions import NodeException
|
17
18
|
from vellum.workflows.nodes.bases import BaseNode
|
@@ -73,11 +74,13 @@ class BaseSearchNode(BaseNode[StateType], Generic[StateType]):
|
|
73
74
|
results: List[SearchResult]
|
74
75
|
|
75
76
|
def _perform_search(self) -> SearchResponse:
|
77
|
+
index_is_uuid = True if isinstance(self.document_index, UUID) else is_valid_uuid(self.document_index)
|
78
|
+
|
76
79
|
try:
|
77
80
|
return self._context.vellum_client.search(
|
78
81
|
query=self.query,
|
79
|
-
index_id=str(self.document_index) if
|
80
|
-
index_name=self.document_index if
|
82
|
+
index_id=str(self.document_index) if index_is_uuid else None,
|
83
|
+
index_name=str(self.document_index) if not index_is_uuid else None,
|
81
84
|
options=self.options,
|
82
85
|
)
|
83
86
|
except NotFoundError:
|
@@ -17,12 +17,13 @@ from vellum.workflows.context import get_parent_context
|
|
17
17
|
from vellum.workflows.errors import WorkflowErrorCode
|
18
18
|
from vellum.workflows.errors.types import workflow_event_error_to_workflow_error
|
19
19
|
from vellum.workflows.exceptions import NodeException
|
20
|
-
from vellum.workflows.nodes.bases.
|
20
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
21
21
|
from vellum.workflows.outputs.base import BaseOutput
|
22
|
+
from vellum.workflows.types.core import EntityInputsInterface
|
22
23
|
from vellum.workflows.types.generics import StateType
|
23
24
|
|
24
25
|
|
25
|
-
class SubworkflowDeploymentNode(
|
26
|
+
class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
|
26
27
|
"""
|
27
28
|
Used to execute a Workflow Deployment.
|
28
29
|
|
@@ -38,6 +39,7 @@ class SubworkflowDeploymentNode(BaseSubworkflowNode[StateType], Generic[StateTyp
|
|
38
39
|
|
39
40
|
# Either the Workflow Deployment's UUID or its name.
|
40
41
|
deployment: ClassVar[Union[UUID, str]]
|
42
|
+
subworkflow_inputs: ClassVar[EntityInputsInterface] = {}
|
41
43
|
|
42
44
|
release_tag: str = LATEST_RELEASE_TAG
|
43
45
|
external_id: Optional[str] = OMIT
|
@@ -0,0 +1,6 @@
|
|
1
|
+
# 🧪 Experimental
|
2
|
+
|
3
|
+
This section is a proofing ground for new ideas and concepts. It's not meant to be used in production and is
|
4
|
+
subject to breaking changes at any time.
|
5
|
+
|
6
|
+
If a concept within is sufficiently interesting and validated, then it may be introduced as a first-class node.
|
File without changes
|
@@ -0,0 +1,260 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
from uuid import uuid4
|
5
|
+
from typing import Any, Iterable, Iterator, List, Literal, Union, cast
|
6
|
+
|
7
|
+
from openai import OpenAI
|
8
|
+
from openai.types.chat import (
|
9
|
+
ChatCompletionAssistantMessageParam,
|
10
|
+
ChatCompletionContentPartImageParam,
|
11
|
+
ChatCompletionContentPartInputAudioParam,
|
12
|
+
ChatCompletionContentPartParam,
|
13
|
+
ChatCompletionContentPartRefusalParam,
|
14
|
+
ChatCompletionContentPartTextParam,
|
15
|
+
ChatCompletionMessageParam,
|
16
|
+
ChatCompletionSystemMessageParam,
|
17
|
+
ChatCompletionUserMessageParam,
|
18
|
+
)
|
19
|
+
from openai.types.chat.chat_completion_chunk import Choice
|
20
|
+
|
21
|
+
from vellum import (
|
22
|
+
AdHocExecutePromptEvent,
|
23
|
+
FulfilledAdHocExecutePromptEvent,
|
24
|
+
InitiatedAdHocExecutePromptEvent,
|
25
|
+
RejectedAdHocExecutePromptEvent,
|
26
|
+
StreamingAdHocExecutePromptEvent,
|
27
|
+
StringVellumValue,
|
28
|
+
VellumAudio,
|
29
|
+
VellumError,
|
30
|
+
)
|
31
|
+
from vellum.prompts.blocks.compilation import compile_prompt_blocks
|
32
|
+
from vellum.prompts.blocks.types import CompiledChatMessagePromptBlock
|
33
|
+
from vellum.workflows.errors import WorkflowErrorCode
|
34
|
+
from vellum.workflows.exceptions import NodeException
|
35
|
+
from vellum.workflows.nodes import InlinePromptNode
|
36
|
+
from vellum.workflows.types.generics import StateType
|
37
|
+
|
38
|
+
logger = logging.getLogger(__name__)
|
39
|
+
|
40
|
+
|
41
|
+
class OpenAIChatCompletionNode(InlinePromptNode[StateType]):
|
42
|
+
"""
|
43
|
+
Used to execute a Prompt using the OpenAI API.
|
44
|
+
"""
|
45
|
+
|
46
|
+
# Override
|
47
|
+
def _get_prompt_event_stream(self) -> Iterator[AdHocExecutePromptEvent]:
|
48
|
+
client = self._get_client()
|
49
|
+
|
50
|
+
execution_id = str(uuid4())
|
51
|
+
|
52
|
+
yield InitiatedAdHocExecutePromptEvent(
|
53
|
+
execution_id=execution_id,
|
54
|
+
)
|
55
|
+
|
56
|
+
try:
|
57
|
+
stream = client.chat.completions.create(
|
58
|
+
messages=self._get_messages(),
|
59
|
+
model=self.ml_model,
|
60
|
+
# TODO: Add support for additional parameters
|
61
|
+
stream=True,
|
62
|
+
)
|
63
|
+
except Exception as exc:
|
64
|
+
yield RejectedAdHocExecutePromptEvent(
|
65
|
+
error=VellumError(
|
66
|
+
code=WorkflowErrorCode.PROVIDER_ERROR,
|
67
|
+
message=exc.args[0],
|
68
|
+
),
|
69
|
+
execution_id=execution_id,
|
70
|
+
)
|
71
|
+
return
|
72
|
+
|
73
|
+
combined_delta_content = ""
|
74
|
+
for chunk in stream:
|
75
|
+
choices: List[Choice] = chunk.choices
|
76
|
+
if len(choices) != 1:
|
77
|
+
yield RejectedAdHocExecutePromptEvent(
|
78
|
+
error=VellumError(
|
79
|
+
code=WorkflowErrorCode.PROVIDER_ERROR,
|
80
|
+
message="Expected one choice per chunk, but found more than one.",
|
81
|
+
),
|
82
|
+
execution_id=execution_id,
|
83
|
+
)
|
84
|
+
return
|
85
|
+
|
86
|
+
choice = choices[0]
|
87
|
+
delta = choice.delta
|
88
|
+
|
89
|
+
if delta.tool_calls:
|
90
|
+
# TODO: Add support for tool calls
|
91
|
+
raise NotImplementedError("This node hasn't been extended to support tool calling yet.")
|
92
|
+
|
93
|
+
if delta.content:
|
94
|
+
combined_delta_content += delta.content
|
95
|
+
|
96
|
+
StreamingAdHocExecutePromptEvent(
|
97
|
+
output=StringVellumValue(value=delta.content),
|
98
|
+
# TODO: Add support for multiple outputs
|
99
|
+
output_index=1,
|
100
|
+
execution_id=execution_id,
|
101
|
+
)
|
102
|
+
|
103
|
+
yield FulfilledAdHocExecutePromptEvent(
|
104
|
+
# TODO: Add support for multiple outputs
|
105
|
+
outputs=[
|
106
|
+
StringVellumValue(value=combined_delta_content),
|
107
|
+
],
|
108
|
+
execution_id=execution_id,
|
109
|
+
)
|
110
|
+
|
111
|
+
def _get_client(self) -> OpenAI:
|
112
|
+
"""Used to retrieve an API client for interacting with the OpenAI API.
|
113
|
+
|
114
|
+
Note: This method can be overridden if you'd like to use your own API client that conforms to the same
|
115
|
+
interfaces as that of OpenAI.
|
116
|
+
"""
|
117
|
+
|
118
|
+
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
119
|
+
|
120
|
+
if not openai_api_key:
|
121
|
+
raise NodeException(
|
122
|
+
code=WorkflowErrorCode.INTERNAL_ERROR,
|
123
|
+
message="Unable to determine an OpenAI API key.",
|
124
|
+
)
|
125
|
+
|
126
|
+
client = OpenAI(api_key=openai_api_key)
|
127
|
+
return client
|
128
|
+
|
129
|
+
def _get_messages(self) -> Iterable[ChatCompletionMessageParam]:
|
130
|
+
input_variables, input_values = self._compile_prompt_inputs()
|
131
|
+
|
132
|
+
compiled_blocks = compile_prompt_blocks(
|
133
|
+
blocks=self.blocks, inputs=input_values, input_variables=input_variables
|
134
|
+
)
|
135
|
+
|
136
|
+
chat_message_blocks: list[CompiledChatMessagePromptBlock] = [
|
137
|
+
block for block in compiled_blocks if block.block_type == "CHAT_MESSAGE"
|
138
|
+
]
|
139
|
+
messages = [self._create_message(block) for block in chat_message_blocks]
|
140
|
+
|
141
|
+
return messages
|
142
|
+
|
143
|
+
@classmethod
|
144
|
+
def _create_message(cls, chat_message_block: CompiledChatMessagePromptBlock) -> ChatCompletionMessageParam:
|
145
|
+
name = chat_message_block.source
|
146
|
+
content = cls._create_message_content(chat_message_block)
|
147
|
+
|
148
|
+
if chat_message_block.role == "SYSTEM":
|
149
|
+
relevant_system_content = [
|
150
|
+
cast(ChatCompletionContentPartTextParam, c) for c in content if c["type"] == "text"
|
151
|
+
]
|
152
|
+
system_message: ChatCompletionSystemMessageParam = {
|
153
|
+
"content": relevant_system_content,
|
154
|
+
"role": "system",
|
155
|
+
}
|
156
|
+
if name:
|
157
|
+
system_message["name"] = name
|
158
|
+
|
159
|
+
return system_message
|
160
|
+
elif chat_message_block.role == "USER":
|
161
|
+
user_message: ChatCompletionUserMessageParam = {
|
162
|
+
"content": content,
|
163
|
+
"role": "user",
|
164
|
+
}
|
165
|
+
if name:
|
166
|
+
user_message["name"] = name
|
167
|
+
|
168
|
+
return user_message
|
169
|
+
elif chat_message_block.role == "ASSISTANT":
|
170
|
+
relevant_assistant_content = [
|
171
|
+
cast(Union[ChatCompletionContentPartTextParam, ChatCompletionContentPartRefusalParam], c)
|
172
|
+
for c in content
|
173
|
+
if c["type"] in ["text", "refusal"]
|
174
|
+
]
|
175
|
+
assistant_message: ChatCompletionAssistantMessageParam = {
|
176
|
+
"content": relevant_assistant_content,
|
177
|
+
"role": "assistant",
|
178
|
+
}
|
179
|
+
if name:
|
180
|
+
assistant_message["name"] = name
|
181
|
+
|
182
|
+
return assistant_message
|
183
|
+
else:
|
184
|
+
logger.error(f"Unexpected role: {chat_message_block.role}")
|
185
|
+
raise NodeException(
|
186
|
+
code=WorkflowErrorCode.INTERNAL_ERROR, message="Unexpected role found when compiling prompt blocks"
|
187
|
+
)
|
188
|
+
|
189
|
+
@classmethod
|
190
|
+
def _create_message_content(
|
191
|
+
cls,
|
192
|
+
chat_message_block: CompiledChatMessagePromptBlock,
|
193
|
+
) -> List[ChatCompletionContentPartParam]:
|
194
|
+
content: List[ChatCompletionContentPartParam] = []
|
195
|
+
for block in chat_message_block.blocks:
|
196
|
+
if block.content.type == "STRING":
|
197
|
+
string_value = cast(str, block.content.value)
|
198
|
+
string_content_item: ChatCompletionContentPartTextParam = {"type": "text", "text": string_value}
|
199
|
+
content.append(string_content_item)
|
200
|
+
elif block.content.type == "JSON":
|
201
|
+
json_value = cast(Any, block.content.value)
|
202
|
+
json_content_item: ChatCompletionContentPartTextParam = {"type": "text", "text": json.dumps(json_value)}
|
203
|
+
content.append(json_content_item)
|
204
|
+
elif block.content.type == "IMAGE":
|
205
|
+
image_value = cast(VellumAudio, block.content.value)
|
206
|
+
image_content_item: ChatCompletionContentPartImageParam = {
|
207
|
+
"type": "image_url",
|
208
|
+
"image_url": {"url": image_value.src},
|
209
|
+
}
|
210
|
+
if image_value.metadata and image_value.metadata.get("detail"):
|
211
|
+
detail = image_value.metadata["detail"]
|
212
|
+
|
213
|
+
if detail not in ["auto", "low", "high"]:
|
214
|
+
raise NodeException(
|
215
|
+
code=WorkflowErrorCode.INTERNAL_ERROR,
|
216
|
+
message="Image detail must be one of 'auto', 'low', or 'high.",
|
217
|
+
)
|
218
|
+
|
219
|
+
image_content_item["image_url"]["detail"] = cast(Literal["auto", "low", "high"], detail)
|
220
|
+
|
221
|
+
content.append(image_content_item)
|
222
|
+
elif block.content.type == "AUDIO":
|
223
|
+
audio_value = cast(VellumAudio, block.content.value)
|
224
|
+
audio_value_src_parts = audio_value.src.split(",")
|
225
|
+
if len(audio_value_src_parts) != 2:
|
226
|
+
raise NodeException(
|
227
|
+
code=WorkflowErrorCode.INTERNAL_ERROR, message="Audio data is not properly encoded."
|
228
|
+
)
|
229
|
+
_, cleaned_audio_value = audio_value_src_parts
|
230
|
+
if not audio_value.metadata:
|
231
|
+
raise NodeException(
|
232
|
+
code=WorkflowErrorCode.INTERNAL_ERROR, message="Audio metadata is required for audio input."
|
233
|
+
)
|
234
|
+
audio_format = audio_value.metadata.get("format")
|
235
|
+
if not audio_format:
|
236
|
+
raise NodeException(
|
237
|
+
code=WorkflowErrorCode.INTERNAL_ERROR, message="Audio format is required for audio input."
|
238
|
+
)
|
239
|
+
if audio_format not in {"wav", "mp3"}:
|
240
|
+
raise NodeException(
|
241
|
+
code=WorkflowErrorCode.INTERNAL_ERROR,
|
242
|
+
message="Audio format must be one of 'wav' or 'mp3'.",
|
243
|
+
)
|
244
|
+
|
245
|
+
audio_content_item: ChatCompletionContentPartInputAudioParam = {
|
246
|
+
"type": "input_audio",
|
247
|
+
"input_audio": {
|
248
|
+
"data": cleaned_audio_value,
|
249
|
+
"format": cast(Literal["wav", "mp3"], audio_format),
|
250
|
+
},
|
251
|
+
}
|
252
|
+
|
253
|
+
content.append(audio_content_item)
|
254
|
+
else:
|
255
|
+
raise NodeException(
|
256
|
+
code=WorkflowErrorCode.INTERNAL_ERROR,
|
257
|
+
message=f"Failed to parse chat message block {block.content.type}",
|
258
|
+
)
|
259
|
+
|
260
|
+
return content
|
vellum/workflows/sandbox.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Generic, Sequence
|
1
|
+
from typing import Generic, Sequence
|
2
2
|
|
3
3
|
import dotenv
|
4
4
|
|
@@ -9,8 +9,8 @@ from vellum.workflows.types.generics import WorkflowType
|
|
9
9
|
from vellum.workflows.workflows.event_filters import root_workflow_event_filter
|
10
10
|
|
11
11
|
|
12
|
-
class
|
13
|
-
def __init__(self, workflow:
|
12
|
+
class WorkflowSandboxRunner(Generic[WorkflowType]):
|
13
|
+
def __init__(self, workflow: WorkflowType, inputs: Sequence[BaseInputs]):
|
14
14
|
if not inputs:
|
15
15
|
raise ValueError("Inputs are required to have at least one defined inputs")
|
16
16
|
|
@@ -30,8 +30,7 @@ class SandboxRunner(Generic[WorkflowType]):
|
|
30
30
|
|
31
31
|
selected_inputs = self._inputs[index]
|
32
32
|
|
33
|
-
|
34
|
-
events = workflow.stream(
|
33
|
+
events = self._workflow.stream(
|
35
34
|
inputs=selected_inputs,
|
36
35
|
event_filter=root_workflow_event_filter,
|
37
36
|
)
|
@@ -49,3 +48,6 @@ class SandboxRunner(Generic[WorkflowType]):
|
|
49
48
|
self._logger.info(f"Just started Node: {event.node_definition.__name__}")
|
50
49
|
elif event.name == "node.execution.fulfilled":
|
51
50
|
self._logger.info(f"Just finished Node: {event.node_definition.__name__}")
|
51
|
+
elif event.name == "node.execution.rejected":
|
52
|
+
self._logger.debug(f"Error: {event.error}")
|
53
|
+
self._logger.error(f"Failed to run Node: {event.node_definition.__name__}")
|
@@ -13,11 +13,12 @@ if TYPE_CHECKING:
|
|
13
13
|
class WorkflowContext:
|
14
14
|
def __init__(
|
15
15
|
self,
|
16
|
-
|
17
|
-
|
16
|
+
*,
|
17
|
+
vellum_client: Optional[Vellum] = None,
|
18
|
+
parent_context: Optional[ParentContext] = None,
|
18
19
|
):
|
19
|
-
self._vellum_client =
|
20
|
-
self._parent_context =
|
20
|
+
self._vellum_client = vellum_client
|
21
|
+
self._parent_context = parent_context
|
21
22
|
self._event_queue: Optional[Queue["WorkflowEvent"]] = None
|
22
23
|
|
23
24
|
@cached_property
|
@@ -3,7 +3,7 @@ from typing import List
|
|
3
3
|
|
4
4
|
from vellum.workflows.inputs.base import BaseInputs
|
5
5
|
from vellum.workflows.nodes.bases.base import BaseNode
|
6
|
-
from vellum.workflows.sandbox import
|
6
|
+
from vellum.workflows.sandbox import WorkflowSandboxRunner
|
7
7
|
from vellum.workflows.state.base import BaseState
|
8
8
|
from vellum.workflows.workflows.base import BaseWorkflow
|
9
9
|
|
@@ -49,7 +49,7 @@ def test_sandbox_runner__happy_path(mock_logger, run_kwargs, expected_last_log):
|
|
49
49
|
]
|
50
50
|
|
51
51
|
# WHEN we run the sandbox
|
52
|
-
runner =
|
52
|
+
runner = WorkflowSandboxRunner(workflow=Workflow(), inputs=inputs)
|
53
53
|
runner.run(**run_kwargs)
|
54
54
|
|
55
55
|
# THEN we see the logs
|
@@ -2,6 +2,7 @@ import pytest
|
|
2
2
|
from typing import List, Optional
|
3
3
|
|
4
4
|
from vellum import ChatMessage, SearchResult
|
5
|
+
from vellum.workflows.types.core import Json
|
5
6
|
from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_variable_type
|
6
7
|
|
7
8
|
|
@@ -18,6 +19,8 @@ from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_var
|
|
18
19
|
(Optional[List[ChatMessage]], "CHAT_HISTORY"),
|
19
20
|
(List[SearchResult], "SEARCH_RESULTS"),
|
20
21
|
(Optional[List[SearchResult]], "SEARCH_RESULTS"),
|
22
|
+
(Json, "JSON"),
|
23
|
+
(Optional[Json], "JSON"),
|
21
24
|
],
|
22
25
|
)
|
23
26
|
def test_primitive_type_to_vellum_variable_type(type_, expected):
|
@@ -35,14 +35,15 @@ def primitive_type_to_vellum_variable_type(type_: Union[Type, BaseDescriptor]) -
|
|
35
35
|
if len(types) != 1:
|
36
36
|
# Check explicitly for our internal JSON type.
|
37
37
|
# Matches the type found at vellum.workflows.utils.vellum_variables.Json
|
38
|
-
|
38
|
+
actual_types_with_explicit_ref = [
|
39
39
|
bool,
|
40
40
|
int,
|
41
41
|
float,
|
42
42
|
str,
|
43
|
-
typing.List[
|
44
|
-
typing.Dict[str,
|
45
|
-
]
|
43
|
+
typing.List[Json],
|
44
|
+
typing.Dict[str, Json],
|
45
|
+
]
|
46
|
+
if types == actual_types_with_explicit_ref:
|
46
47
|
return "JSON"
|
47
48
|
raise ValueError(f"Expected Descriptor to only have one type, got {types}")
|
48
49
|
|
@@ -121,10 +121,11 @@ class BaseWorkflow(Generic[WorkflowInputsType, StateType], metaclass=_BaseWorkfl
|
|
121
121
|
|
122
122
|
def __init__(
|
123
123
|
self,
|
124
|
+
*,
|
125
|
+
context: Optional[WorkflowContext] = None,
|
124
126
|
parent_state: Optional[BaseState] = None,
|
125
127
|
emitters: Optional[List[BaseWorkflowEmitter]] = None,
|
126
128
|
resolvers: Optional[List[BaseWorkflowResolver]] = None,
|
127
|
-
context: Optional[WorkflowContext] = None,
|
128
129
|
):
|
129
130
|
self._parent_state = parent_state
|
130
131
|
self.emitters = emitters or (self.emitters if hasattr(self, "emitters") else [])
|
@@ -188,6 +189,7 @@ class BaseWorkflow(Generic[WorkflowInputsType, StateType], metaclass=_BaseWorkfl
|
|
188
189
|
def run(
|
189
190
|
self,
|
190
191
|
inputs: Optional[WorkflowInputsType] = None,
|
192
|
+
*,
|
191
193
|
state: Optional[StateType] = None,
|
192
194
|
entrypoint_nodes: Optional[RunFromNodeArg] = None,
|
193
195
|
external_inputs: Optional[ExternalInputsArg] = None,
|
@@ -281,8 +283,9 @@ class BaseWorkflow(Generic[WorkflowInputsType, StateType], metaclass=_BaseWorkfl
|
|
281
283
|
|
282
284
|
def stream(
|
283
285
|
self,
|
284
|
-
event_filter: Optional[Callable[[Type["BaseWorkflow"], WorkflowEvent], bool]] = None,
|
285
286
|
inputs: Optional[WorkflowInputsType] = None,
|
287
|
+
*,
|
288
|
+
event_filter: Optional[Callable[[Type["BaseWorkflow"], WorkflowEvent], bool]] = None,
|
286
289
|
state: Optional[StateType] = None,
|
287
290
|
entrypoint_nodes: Optional[RunFromNodeArg] = None,
|
288
291
|
external_inputs: Optional[ExternalInputsArg] = None,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: vellum-ai
|
3
|
-
Version: 0.12.
|
3
|
+
Version: 0.12.9
|
4
4
|
Summary:
|
5
5
|
License: MIT
|
6
6
|
Requires-Python: >=3.9,<4.0
|
@@ -25,6 +25,7 @@ Requires-Dist: cdktf (>=0.20.5,<0.21.0)
|
|
25
25
|
Requires-Dist: click (==8.1.7)
|
26
26
|
Requires-Dist: docker (==7.1.0)
|
27
27
|
Requires-Dist: httpx (>=0.21.2)
|
28
|
+
Requires-Dist: openai (>=1.0.0)
|
28
29
|
Requires-Dist: orderly-set (>=5.2.2,<6.0.0)
|
29
30
|
Requires-Dist: publication (==0.0.3)
|
30
31
|
Requires-Dist: pydantic (>=1.9.2)
|