vellum-ai 0.12.7__py3-none-any.whl → 0.12.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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)
|