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.
Files changed (50) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/evaluations/resources.py +2 -2
  3. vellum/prompts/__init__.py +0 -0
  4. vellum/prompts/blocks/__init__.py +0 -0
  5. vellum/prompts/blocks/compilation.py +190 -0
  6. vellum/prompts/blocks/exceptions.py +2 -0
  7. vellum/prompts/blocks/tests/__init__.py +0 -0
  8. vellum/prompts/blocks/tests/test_compilation.py +110 -0
  9. vellum/prompts/blocks/types.py +36 -0
  10. vellum/utils/__init__.py +0 -0
  11. vellum/utils/templating/__init__.py +0 -0
  12. vellum/utils/templating/constants.py +28 -0
  13. vellum/{workflows/nodes/core/templating_node → utils/templating}/render.py +1 -1
  14. vellum/workflows/nodes/bases/__init__.py +0 -2
  15. vellum/workflows/nodes/bases/base.py +2 -6
  16. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +13 -7
  17. vellum/workflows/nodes/core/inline_subworkflow_node/tests/__init__.py +0 -0
  18. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +41 -0
  19. vellum/workflows/nodes/core/map_node/node.py +1 -1
  20. vellum/workflows/nodes/core/templating_node/node.py +12 -31
  21. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +66 -0
  22. vellum/workflows/nodes/core/try_node/node.py +1 -3
  23. vellum/workflows/nodes/core/try_node/tests/test_node.py +1 -1
  24. vellum/workflows/nodes/displayable/bases/api_node/node.py +2 -2
  25. vellum/workflows/nodes/displayable/bases/search_node.py +5 -2
  26. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +4 -2
  27. vellum/workflows/nodes/experimental/README.md +6 -0
  28. vellum/workflows/nodes/experimental/__init__.py +0 -0
  29. vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py +5 -0
  30. vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py +260 -0
  31. vellum/workflows/sandbox.py +7 -5
  32. vellum/workflows/state/context.py +5 -4
  33. vellum/workflows/tests/test_sandbox.py +2 -2
  34. vellum/workflows/utils/tests/test_vellum_variables.py +3 -0
  35. vellum/workflows/utils/vellum_variables.py +5 -4
  36. vellum/workflows/workflows/base.py +5 -2
  37. {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/METADATA +2 -1
  38. {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/RECORD +48 -33
  39. vellum_cli/tests/test_push.py +1 -1
  40. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +6 -1
  41. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +207 -0
  42. vellum/workflows/nodes/bases/base_subworkflow_node/__init__.py +0 -5
  43. vellum/workflows/nodes/bases/base_subworkflow_node/node.py +0 -10
  44. /vellum/{workflows/nodes/core/templating_node → utils/templating}/custom_filters.py +0 -0
  45. /vellum/{workflows/nodes/core/templating_node → utils/templating}/exceptions.py +0 -0
  46. /vellum/{evaluations/utils → utils}/typing.py +0 -0
  47. /vellum/{evaluations/utils → utils}/uuid.py +0 -0
  48. {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/LICENSE +0 -0
  49. {vellum_ai-0.12.7.dist-info → vellum_ai-0.12.9.dist-info}/WHEEL +0 -0
  50. {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
- _vellum_client=Vellum(api_key="test-key"),
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["Json"] = None
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["Json"]
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 isinstance(self.document_index, UUID) else None,
80
- index_name=self.document_index if isinstance(self.document_index, str) else None,
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.base_subworkflow_node.node import BaseSubworkflowNode
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(BaseSubworkflowNode[StateType], Generic[StateType]):
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,5 @@
1
+ from .node import OpenAIChatCompletionNode
2
+
3
+ __all__ = [
4
+ "OpenAIChatCompletionNode",
5
+ ]
@@ -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
@@ -1,4 +1,4 @@
1
- from typing import Generic, Sequence, Type
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 SandboxRunner(Generic[WorkflowType]):
13
- def __init__(self, workflow: Type[WorkflowType], inputs: Sequence[BaseInputs]):
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
- workflow = self._workflow()
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
- _vellum_client: Optional[Vellum] = None,
17
- _parent_context: Optional[ParentContext] = None,
16
+ *,
17
+ vellum_client: Optional[Vellum] = None,
18
+ parent_context: Optional[ParentContext] = None,
18
19
  ):
19
- self._vellum_client = _vellum_client
20
- self._parent_context = _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 SandboxRunner
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 = SandboxRunner(workflow=Workflow, inputs=inputs)
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
- if types == [
38
+ actual_types_with_explicit_ref = [
39
39
  bool,
40
40
  int,
41
41
  float,
42
42
  str,
43
- typing.List[typing.ForwardRef("Json")], # type: ignore [misc]
44
- typing.Dict[str, typing.ForwardRef("Json")], # type: ignore [misc]
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.7
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)