vellum-ai 1.2.1__py3-none-any.whl → 1.2.3__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.
Files changed (128) hide show
  1. vellum/__init__.py +40 -0
  2. vellum/client/core/client_wrapper.py +2 -2
  3. vellum/client/core/pydantic_utilities.py +3 -2
  4. vellum/client/reference.md +16 -0
  5. vellum/client/resources/workflow_executions/client.py +28 -4
  6. vellum/client/resources/workflow_executions/raw_client.py +32 -2
  7. vellum/client/types/__init__.py +40 -0
  8. vellum/client/types/audio_input_request.py +30 -0
  9. vellum/client/types/delimiter_chunker_config.py +20 -0
  10. vellum/client/types/delimiter_chunker_config_request.py +20 -0
  11. vellum/client/types/delimiter_chunking.py +21 -0
  12. vellum/client/types/delimiter_chunking_request.py +21 -0
  13. vellum/client/types/document_index_chunking.py +4 -1
  14. vellum/client/types/document_index_chunking_request.py +2 -1
  15. vellum/client/types/document_input_request.py +30 -0
  16. vellum/client/types/execution_audio_vellum_value.py +31 -0
  17. vellum/client/types/execution_document_vellum_value.py +31 -0
  18. vellum/client/types/execution_image_vellum_value.py +31 -0
  19. vellum/client/types/execution_vellum_value.py +8 -0
  20. vellum/client/types/execution_video_vellum_value.py +31 -0
  21. vellum/client/types/image_input_request.py +30 -0
  22. vellum/client/types/logical_operator.py +1 -0
  23. vellum/client/types/node_input_compiled_audio_value.py +23 -0
  24. vellum/client/types/node_input_compiled_document_value.py +23 -0
  25. vellum/client/types/node_input_compiled_image_value.py +23 -0
  26. vellum/client/types/node_input_compiled_video_value.py +23 -0
  27. vellum/client/types/node_input_variable_compiled_value.py +8 -0
  28. vellum/client/types/prompt_deployment_input_request.py +13 -1
  29. vellum/client/types/prompt_request_audio_input.py +26 -0
  30. vellum/client/types/prompt_request_document_input.py +26 -0
  31. vellum/client/types/prompt_request_image_input.py +26 -0
  32. vellum/client/types/prompt_request_input.py +13 -1
  33. vellum/client/types/prompt_request_video_input.py +26 -0
  34. vellum/client/types/video_input_request.py +30 -0
  35. vellum/prompts/blocks/compilation.py +13 -11
  36. vellum/types/audio_input_request.py +3 -0
  37. vellum/types/delimiter_chunker_config.py +3 -0
  38. vellum/types/delimiter_chunker_config_request.py +3 -0
  39. vellum/types/delimiter_chunking.py +3 -0
  40. vellum/types/delimiter_chunking_request.py +3 -0
  41. vellum/types/document_input_request.py +3 -0
  42. vellum/types/execution_audio_vellum_value.py +3 -0
  43. vellum/types/execution_document_vellum_value.py +3 -0
  44. vellum/types/execution_image_vellum_value.py +3 -0
  45. vellum/types/execution_video_vellum_value.py +3 -0
  46. vellum/types/image_input_request.py +3 -0
  47. vellum/types/node_input_compiled_audio_value.py +3 -0
  48. vellum/types/node_input_compiled_document_value.py +3 -0
  49. vellum/types/node_input_compiled_image_value.py +3 -0
  50. vellum/types/node_input_compiled_video_value.py +3 -0
  51. vellum/types/prompt_request_audio_input.py +3 -0
  52. vellum/types/prompt_request_document_input.py +3 -0
  53. vellum/types/prompt_request_image_input.py +3 -0
  54. vellum/types/prompt_request_video_input.py +3 -0
  55. vellum/types/video_input_request.py +3 -0
  56. vellum/workflows/context.py +27 -9
  57. vellum/workflows/emitters/vellum_emitter.py +16 -69
  58. vellum/workflows/events/context.py +53 -78
  59. vellum/workflows/events/node.py +5 -5
  60. vellum/workflows/events/relational_threads.py +41 -0
  61. vellum/workflows/events/tests/test_basic_workflow.py +50 -0
  62. vellum/workflows/events/tests/test_event.py +1 -0
  63. vellum/workflows/events/workflow.py +15 -1
  64. vellum/workflows/expressions/contains.py +7 -0
  65. vellum/workflows/expressions/tests/test_contains.py +175 -0
  66. vellum/workflows/graph/graph.py +52 -8
  67. vellum/workflows/graph/tests/test_graph.py +17 -0
  68. vellum/workflows/integrations/mcp_service.py +35 -5
  69. vellum/workflows/integrations/tests/test_mcp_service.py +81 -0
  70. vellum/workflows/nodes/bases/base.py +0 -1
  71. vellum/workflows/nodes/core/error_node/node.py +4 -0
  72. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +35 -0
  73. vellum/workflows/nodes/core/map_node/node.py +7 -0
  74. vellum/workflows/nodes/core/map_node/tests/test_node.py +19 -0
  75. vellum/workflows/nodes/displayable/bases/utils.py +4 -2
  76. vellum/workflows/nodes/displayable/final_output_node/node.py +4 -0
  77. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +88 -2
  78. vellum/workflows/nodes/displayable/tool_calling_node/node.py +1 -0
  79. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +85 -1
  80. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py +12 -0
  81. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +5 -2
  82. vellum/workflows/ports/node_ports.py +3 -0
  83. vellum/workflows/ports/port.py +8 -11
  84. vellum/workflows/state/context.py +47 -2
  85. vellum/workflows/types/definition.py +4 -4
  86. vellum/workflows/utils/uuids.py +15 -0
  87. vellum/workflows/utils/vellum_variables.py +5 -3
  88. vellum/workflows/workflows/base.py +1 -0
  89. {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/METADATA +1 -1
  90. {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/RECORD +128 -82
  91. vellum_ee/workflows/display/nodes/base_node_display.py +19 -10
  92. vellum_ee/workflows/display/nodes/vellum/api_node.py +1 -4
  93. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -4
  94. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +1 -4
  95. vellum_ee/workflows/display/nodes/vellum/error_node.py +1 -3
  96. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +1 -3
  97. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +1 -4
  98. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +1 -8
  99. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +1 -4
  100. vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -4
  101. vellum_ee/workflows/display/nodes/vellum/merge_node.py +1 -4
  102. vellum_ee/workflows/display/nodes/vellum/note_node.py +2 -4
  103. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +1 -4
  104. vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -4
  105. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -4
  106. vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -4
  107. vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +1 -0
  108. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +4 -0
  109. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +12 -0
  110. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +16 -0
  111. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +5 -0
  112. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +4 -0
  113. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +4 -0
  114. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +4 -0
  115. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +4 -0
  116. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +12 -0
  117. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +4 -0
  118. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +4 -0
  119. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +4 -0
  120. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +5 -0
  121. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +1 -0
  122. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +5 -0
  123. vellum_ee/workflows/display/utils/events.py +24 -0
  124. vellum_ee/workflows/display/utils/tests/test_events.py +69 -0
  125. vellum_ee/workflows/tests/test_server.py +95 -0
  126. {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/LICENSE +0 -0
  127. {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/WHEEL +0 -0
  128. {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from uuid import UUID
3
- from typing import Any, ClassVar, Dict, Generic, Iterator, List, Optional, Set, Union, cast
3
+ from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generic, Iterator, List, Optional, Set, Union, cast
4
4
 
5
5
  from vellum import (
6
6
  ChatMessage,
@@ -16,16 +16,21 @@ from vellum.client.core import RequestOptions
16
16
  from vellum.client.core.api_error import ApiError
17
17
  from vellum.client.types.chat_message_request import ChatMessageRequest
18
18
  from vellum.workflows.constants import LATEST_RELEASE_TAG, OMIT
19
- from vellum.workflows.context import get_execution_context
19
+ from vellum.workflows.context import execution_context, get_execution_context, get_parent_context
20
20
  from vellum.workflows.errors import WorkflowErrorCode
21
21
  from vellum.workflows.errors.types import workflow_event_error_to_workflow_error
22
22
  from vellum.workflows.events.types import default_serializer
23
+ from vellum.workflows.events.workflow import is_workflow_event
23
24
  from vellum.workflows.exceptions import NodeException
24
25
  from vellum.workflows.inputs.base import BaseInputs
25
26
  from vellum.workflows.nodes.bases.base import BaseNode
26
27
  from vellum.workflows.outputs.base import BaseOutput
27
28
  from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior
28
29
  from vellum.workflows.types.generics import StateType
30
+ from vellum.workflows.workflows.event_filters import all_workflow_event_filter
31
+
32
+ if TYPE_CHECKING:
33
+ from vellum.workflows.workflows.base import BaseWorkflow
29
34
 
30
35
 
31
36
  class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
@@ -135,6 +140,74 @@ class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
135
140
  )
136
141
  )
137
142
 
143
+ def _compile_subworkflow_inputs_for_direct_invocation(self, workflow: "BaseWorkflow") -> Any:
144
+ """Compile inputs for direct workflow invocation (similar to InlineSubworkflowNode)."""
145
+ inputs_class = workflow.get_inputs_class()
146
+
147
+ if isinstance(self.subworkflow_inputs, BaseInputs):
148
+ inputs_dict = {}
149
+ for input_descriptor, input_value in self.subworkflow_inputs:
150
+ if input_value is not None:
151
+ inputs_dict[input_descriptor.name] = input_value
152
+ return inputs_class(**inputs_dict)
153
+ else:
154
+ # Filter out None values for direct invocation
155
+ filtered_inputs = {k: v for k, v in self.subworkflow_inputs.items() if v is not None}
156
+ return inputs_class(**filtered_inputs)
157
+
158
+ def _run_resolved_workflow(self, resolved_workflow: "BaseWorkflow") -> Iterator[BaseOutput]:
159
+ """Execute resolved workflow directly (similar to InlineSubworkflowNode)."""
160
+ with execution_context(parent_context=get_parent_context()):
161
+ subworkflow_stream = resolved_workflow.stream(
162
+ inputs=self._compile_subworkflow_inputs_for_direct_invocation(resolved_workflow),
163
+ event_filter=all_workflow_event_filter,
164
+ node_output_mocks=self._context._get_all_node_output_mocks(),
165
+ )
166
+
167
+ outputs = None
168
+ exception = None
169
+ fulfilled_output_names: Set[str] = set()
170
+
171
+ for event in subworkflow_stream:
172
+ self._context._emit_subworkflow_event(event)
173
+ if exception:
174
+ continue
175
+
176
+ if not is_workflow_event(event):
177
+ continue
178
+ if event.workflow_definition != resolved_workflow.__class__:
179
+ continue
180
+
181
+ if event.name == "workflow.execution.streaming":
182
+ if event.output.is_fulfilled:
183
+ fulfilled_output_names.add(event.output.name)
184
+ yield event.output
185
+ elif event.name == "workflow.execution.fulfilled":
186
+ outputs = event.outputs
187
+ elif event.name == "workflow.execution.rejected":
188
+ exception = NodeException.of(event.error)
189
+ elif event.name == "workflow.execution.paused":
190
+ exception = NodeException(
191
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
192
+ message="Subworkflow unexpectedly paused",
193
+ )
194
+
195
+ if exception:
196
+ raise exception
197
+
198
+ if outputs is None:
199
+ raise NodeException(
200
+ message="Expected to receive outputs from Workflow Deployment",
201
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
202
+ )
203
+
204
+ for output_descriptor, output_value in outputs:
205
+ if output_descriptor.name not in fulfilled_output_names:
206
+ yield BaseOutput(
207
+ name=output_descriptor.name,
208
+ value=output_value,
209
+ )
210
+
138
211
  def run(self) -> Iterator[BaseOutput]:
139
212
  execution_context = get_execution_context()
140
213
  request_options = self.request_options or RequestOptions()
@@ -152,6 +225,19 @@ class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
152
225
  message="Expected subworkflow deployment attribute to be either a UUID or STR, got None instead",
153
226
  )
154
227
 
228
+ if not deployment_name:
229
+ raise NodeException(
230
+ code=WorkflowErrorCode.INVALID_INPUTS,
231
+ message="Expected deployment name to be provided for subworkflow execution.",
232
+ )
233
+
234
+ resolved_workflow = self._context.resolve_workflow_deployment(
235
+ deployment_name=deployment_name, release_tag=self.release_tag, state=self.state
236
+ )
237
+ if resolved_workflow:
238
+ yield from self._run_resolved_workflow(resolved_workflow)
239
+ return
240
+
155
241
  try:
156
242
  subworkflow_stream = self._context.vellum_client.execute_workflow_stream(
157
243
  inputs=self._compile_subworkflow_inputs(),
@@ -76,6 +76,7 @@ class ToolCallingNode(BaseNode[StateType], Generic[StateType]):
76
76
 
77
77
  class ToolCallingWorkflow(BaseWorkflow[BaseInputs, ToolCallingState]):
78
78
  graph = self._graph
79
+ is_dynamic = True
79
80
 
80
81
  class Outputs(BaseWorkflow.Outputs):
81
82
  text: str = self.tool_prompt_node.Outputs.text
@@ -1,12 +1,14 @@
1
1
  import json
2
2
  from uuid import uuid4
3
- from typing import Any, Iterator
3
+ from typing import Any, Iterator, List
4
4
 
5
5
  from vellum import ChatMessage
6
+ from vellum.client.types.execute_prompt_event import ExecutePromptEvent
6
7
  from vellum.client.types.fulfilled_execute_prompt_event import FulfilledExecutePromptEvent
7
8
  from vellum.client.types.function_call import FunctionCall
8
9
  from vellum.client.types.function_call_vellum_value import FunctionCallVellumValue
9
10
  from vellum.client.types.initiated_execute_prompt_event import InitiatedExecutePromptEvent
11
+ from vellum.client.types.prompt_output import PromptOutput
10
12
  from vellum.client.types.string_chat_message_content import StringChatMessageContent
11
13
  from vellum.client.types.string_vellum_value import StringVellumValue
12
14
  from vellum.client.types.variable_prompt_block import VariablePromptBlock
@@ -25,6 +27,7 @@ from vellum.workflows.outputs.base import BaseOutputs
25
27
  from vellum.workflows.state.base import BaseState, StateMeta
26
28
  from vellum.workflows.state.context import WorkflowContext
27
29
  from vellum.workflows.types.definition import DeploymentDefinition
30
+ from vellum.workflows.workflows.event_filters import all_workflow_event_filter
28
31
 
29
32
 
30
33
  def first_function() -> str:
@@ -238,3 +241,84 @@ def test_tool_calling_node_with_generic_type_parameter():
238
241
  assert node is not None
239
242
  assert isinstance(node, TestToolCallingNode)
240
243
  assert node.state == state
244
+
245
+
246
+ def test_tool_calling_node_workflow_is_dynamic(vellum_adhoc_prompt_client):
247
+ """
248
+ Test workflow_version_exec_config without any mocks to see if that's the issue.
249
+ """
250
+
251
+ def generate_prompt_events(*args, **kwargs) -> Iterator[ExecutePromptEvent]:
252
+ execution_id = str(uuid4())
253
+
254
+ call_count = vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.call_count
255
+ expected_outputs: List[PromptOutput]
256
+ if call_count == 1:
257
+ expected_outputs = [
258
+ FunctionCallVellumValue(
259
+ value=FunctionCall(
260
+ arguments={"var_1": 1, "var_2": 2},
261
+ id="call_123",
262
+ name="add_numbers_workflow",
263
+ state="FULFILLED",
264
+ ),
265
+ ),
266
+ ]
267
+ else:
268
+ expected_outputs = [StringVellumValue(value="The result is 3")]
269
+
270
+ events: List[ExecutePromptEvent] = [
271
+ InitiatedExecutePromptEvent(execution_id=execution_id),
272
+ FulfilledExecutePromptEvent(
273
+ execution_id=execution_id,
274
+ outputs=expected_outputs,
275
+ ),
276
+ ]
277
+ yield from events
278
+
279
+ vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
280
+
281
+ class AddNode(BaseNode):
282
+
283
+ class Outputs(BaseNode.Outputs):
284
+ result: int
285
+
286
+ def run(self) -> Outputs:
287
+ return self.Outputs(result=1)
288
+
289
+ class AddNumbersWorkflow(BaseWorkflow[BaseInputs, BaseState]):
290
+ """
291
+ A simple workflow that adds two numbers.
292
+ """
293
+
294
+ graph = AddNode
295
+
296
+ class Outputs(BaseWorkflow.Outputs):
297
+ result = AddNode.Outputs.result
298
+
299
+ class TestToolCallingNode(ToolCallingNode):
300
+ ml_model = "gpt-4o-mini"
301
+ blocks = []
302
+ functions = [AddNumbersWorkflow]
303
+ prompt_inputs = {}
304
+
305
+ # GIVEN a workflow with just a tool calling node
306
+ class ToolCallingWorkflow(BaseWorkflow[BaseInputs, BaseState]):
307
+ graph = TestToolCallingNode
308
+
309
+ class Outputs(BaseWorkflow.Outputs):
310
+ text: str = TestToolCallingNode.Outputs.text
311
+ chat_history: List[ChatMessage] = TestToolCallingNode.Outputs.chat_history
312
+
313
+ workflow = ToolCallingWorkflow()
314
+
315
+ # WHEN the workflow is executed and we capture all events
316
+ events = list(workflow.stream(event_filter=all_workflow_event_filter))
317
+
318
+ # AND we should find workflow execution initiated events
319
+ initiated_events = [event for event in events if event.name == "workflow.execution.initiated"]
320
+ assert len(initiated_events) == 3 # Main workflow + tool calling internal + inline workflow
321
+
322
+ assert initiated_events[0].body.workflow_definition.is_dynamic is False # Main workflow
323
+ assert initiated_events[1].body.workflow_definition.is_dynamic is True # Tool calling internal
324
+ assert initiated_events[2].body.workflow_definition.is_dynamic is True # Inline workflow
@@ -238,3 +238,15 @@ def test_create_tool_prompt_node_chat_history_block_dict(vellum_adhoc_prompt_cli
238
238
  ),
239
239
  VariablePromptBlock(block_type="VARIABLE", state=None, cache_config=None, input_variable="chat_history"),
240
240
  ]
241
+
242
+
243
+ def test_get_mcp_tool_name_snake_case():
244
+ """Test MCPToolDefinition function name generation with snake case."""
245
+ mcp_tool = MCPToolDefinition(
246
+ name="create_repository",
247
+ server=MCPServer(name="Github Server", url="https://api.github.com"),
248
+ parameters={"repository_name": "string", "description": "string"},
249
+ )
250
+
251
+ result = get_mcp_tool_name(mcp_tool)
252
+ assert result == "github_server__create_repository"
@@ -505,6 +505,7 @@ def create_function_node(
505
505
  )
506
506
  return node
507
507
  elif is_workflow_class(function):
508
+ function.is_dynamic = True
508
509
  node = type(
509
510
  f"DynamicInlineSubworkflowNode_{function.__name__}",
510
511
  (DynamicInlineSubworkflowNode,),
@@ -574,10 +575,12 @@ def get_function_name(function: ToolBase) -> str:
574
575
  name = str(function.deployment_id or function.deployment_name)
575
576
  return name.replace("-", "")
576
577
  elif isinstance(function, ComposioToolDefinition):
577
- return function.name
578
+ # model post init sets the name to the action if it's not set
579
+ return function.name # type: ignore[return-value]
578
580
  else:
579
581
  return snake_case(function.__name__)
580
582
 
581
583
 
582
584
  def get_mcp_tool_name(tool_def: MCPToolDefinition) -> str:
583
- return f"{tool_def.server.name}__{tool_def.name}"
585
+ server_name = snake_case(tool_def.server.name)
586
+ return f"{server_name}__{tool_def.name}"
@@ -40,6 +40,9 @@ class NodePorts(metaclass=_NodePortsMeta):
40
40
 
41
41
  invoked_ports: Set[Port] = set()
42
42
  all_ports = [port for port in self.__class__]
43
+ if not all_ports:
44
+ return set()
45
+
43
46
  enforce_single_invoked_conditional_port = validate_ports(all_ports)
44
47
 
45
48
  for port in all_ports:
@@ -7,8 +7,9 @@ from vellum.workflows.descriptors.base import BaseDescriptor
7
7
  from vellum.workflows.descriptors.exceptions import InvalidExpressionException
8
8
  from vellum.workflows.edges.edge import Edge
9
9
  from vellum.workflows.errors.types import WorkflowErrorCode
10
- from vellum.workflows.exceptions import NodeException, WorkflowInitializationException
10
+ from vellum.workflows.exceptions import NodeException
11
11
  from vellum.workflows.graph import Graph, GraphTarget
12
+ from vellum.workflows.graph.graph import NoPortsNode
12
13
  from vellum.workflows.state.base import BaseState
13
14
  from vellum.workflows.types.core import ConditionType
14
15
 
@@ -66,6 +67,12 @@ class Port:
66
67
  if isinstance(other, Port):
67
68
  return Graph.from_port(self) >> Graph.from_port(other)
68
69
 
70
+ if isinstance(other, NoPortsNode):
71
+ raise ValueError(
72
+ f"Cannot create edge to {other.node_class.__name__} because it has no ports defined. "
73
+ f"Nodes with empty Ports classes cannot be connected to other nodes."
74
+ )
75
+
69
76
  edge = Edge(from_port=self, to_node=other)
70
77
  if edge not in self._edges:
71
78
  self._edges.append(edge)
@@ -107,13 +114,3 @@ class Port:
107
114
  cls, source_type: Type[Any], handler: GetCoreSchemaHandler
108
115
  ) -> core_schema.CoreSchema:
109
116
  return core_schema.is_instance_schema(cls)
110
-
111
- def validate(self):
112
- if (
113
- not self.default
114
- and self._condition_type in (ConditionType.IF, ConditionType.ELIF)
115
- and self._condition is None
116
- ):
117
- raise WorkflowInitializationException(
118
- f"Class {self.node_class.__name__}'s {self.name} should have a defined condition and cannot be empty."
119
- )
@@ -4,15 +4,18 @@ from uuid import uuid4
4
4
  from typing import TYPE_CHECKING, Dict, List, Optional, Type
5
5
 
6
6
  from vellum import Vellum
7
- from vellum.workflows.context import ExecutionContext, get_execution_context
7
+ from vellum.workflows.context import ExecutionContext, get_execution_context, set_execution_context
8
8
  from vellum.workflows.events.types import ExternalParentContext
9
9
  from vellum.workflows.nodes.mocks import MockNodeExecution, MockNodeExecutionArg
10
10
  from vellum.workflows.outputs.base import BaseOutputs
11
11
  from vellum.workflows.references.constant import ConstantValueReference
12
+ from vellum.workflows.utils.uuids import generate_workflow_deployment_prefix
12
13
  from vellum.workflows.vellum_client import create_vellum_client
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from vellum.workflows.events.workflow import WorkflowEvent
17
+ from vellum.workflows.state.base import BaseState
18
+ from vellum.workflows.workflows.base import BaseWorkflow
16
19
 
17
20
 
18
21
  class WorkflowContext:
@@ -22,11 +25,13 @@ class WorkflowContext:
22
25
  vellum_client: Optional[Vellum] = None,
23
26
  execution_context: Optional[ExecutionContext] = None,
24
27
  generated_files: Optional[dict[str, str]] = None,
28
+ namespace: Optional[str] = None,
25
29
  ):
26
30
  self._vellum_client = vellum_client
27
31
  self._event_queue: Optional[Queue["WorkflowEvent"]] = None
28
32
  self._node_output_mocks_map: Dict[Type[BaseOutputs], List[MockNodeExecution]] = {}
29
33
  self._execution_context = get_execution_context()
34
+ self._namespace = namespace
30
35
 
31
36
  if execution_context is not None:
32
37
  self._execution_context.trace_id = execution_context.trace_id
@@ -35,6 +40,8 @@ class WorkflowContext:
35
40
 
36
41
  if self._execution_context.parent_context is None:
37
42
  self._execution_context.parent_context = ExternalParentContext(span_id=uuid4())
43
+ # Propagate the updated context back to the global execution context
44
+ set_execution_context(self._execution_context)
38
45
 
39
46
  self._generated_files = generated_files
40
47
 
@@ -53,6 +60,10 @@ class WorkflowContext:
53
60
  def generated_files(self) -> Optional[dict[str, str]]:
54
61
  return self._generated_files
55
62
 
63
+ @cached_property
64
+ def namespace(self) -> Optional[str]:
65
+ return self._namespace
66
+
56
67
  @cached_property
57
68
  def node_output_mocks_map(self) -> Dict[Type[BaseOutputs], List[MockNodeExecution]]:
58
69
  return self._node_output_mocks_map
@@ -131,6 +142,40 @@ class WorkflowContext:
131
142
  def _get_all_node_output_mocks(self) -> List[MockNodeExecution]:
132
143
  return [mock for mocks in self._node_output_mocks_map.values() for mock in mocks]
133
144
 
145
+ def resolve_workflow_deployment(
146
+ self, deployment_name: str, release_tag: str, state: "BaseState"
147
+ ) -> Optional["BaseWorkflow"]:
148
+ """
149
+ Resolve a workflow deployment by name and release tag.
150
+
151
+ Args:
152
+ deployment_name: The name of the workflow deployment
153
+ release_tag: The release tag to resolve
154
+ state: The base state to pass to the workflow
155
+
156
+ Returns:
157
+ BaseWorkflow instance if found, None otherwise
158
+ """
159
+ if not self.generated_files or not self.namespace:
160
+ return None
161
+
162
+ expected_prefix = generate_workflow_deployment_prefix(deployment_name, release_tag)
163
+
164
+ workflow_file_key = f"{expected_prefix}/workflow.py"
165
+ if workflow_file_key not in self.generated_files:
166
+ return None
167
+
168
+ try:
169
+ from vellum.workflows.workflows.base import BaseWorkflow
170
+
171
+ WorkflowClass = BaseWorkflow.load_from_module(f"{self.namespace}.{expected_prefix}")
172
+ workflow_instance = WorkflowClass(context=WorkflowContext.create_from(self), parent_state=state)
173
+ return workflow_instance
174
+ except Exception:
175
+ return None
176
+
134
177
  @classmethod
135
178
  def create_from(cls, context):
136
- return cls(vellum_client=context.vellum_client, generated_files=context.generated_files)
179
+ return cls(
180
+ vellum_client=context.vellum_client, generated_files=context.generated_files, namespace=context.namespace
181
+ )
@@ -111,11 +111,11 @@ class ComposioToolDefinition(UniversalBaseModel):
111
111
  action: str # Specific action like "GITHUB_CREATE_AN_ISSUE"
112
112
  description: str
113
113
  user_id: Optional[str] = None
114
+ name: str = ""
114
115
 
115
- @property
116
- def name(self) -> str:
117
- """Generate a function name for this tool"""
118
- return self.action.lower()
116
+ def model_post_init(self, __context: Any):
117
+ if self.name == "":
118
+ self.name = self.action.lower()
119
119
 
120
120
 
121
121
  class MCPServer(UniversalBaseModel):
@@ -2,6 +2,21 @@ import hashlib
2
2
  from uuid import UUID
3
3
 
4
4
 
5
+ def generate_workflow_deployment_prefix(deployment_name: str, release_tag: str) -> str:
6
+ """
7
+ Generate a workflow deployment prefix from deployment name and release tag.
8
+
9
+ Args:
10
+ deployment_name: The name of the workflow deployment
11
+ release_tag: The release tag to resolve
12
+
13
+ Returns:
14
+ The generated prefix in format vellum_workflow_deployment_{hash}
15
+ """
16
+ expected_hash = str(uuid4_from_hash(f"{deployment_name}|{release_tag}")).replace("-", "_")
17
+ return f"vellum_workflow_deployment_{expected_hash}"
18
+
19
+
5
20
  def uuid4_from_hash(input_str: str) -> UUID:
6
21
  # Create a SHA-256 hash of the input string
7
22
  hash_bytes = hashlib.sha256(input_str.encode()).digest()
@@ -1,7 +1,7 @@
1
1
  import typing
2
2
  from typing import List, Tuple, Type, Union, get_args, get_origin
3
3
 
4
- from vellum import ( # VellumVideo,; VellumVideoRequest,
4
+ from vellum import (
5
5
  ChatMessage,
6
6
  ChatMessageRequest,
7
7
  FunctionCall,
@@ -19,6 +19,8 @@ from vellum import ( # VellumVideo,; VellumVideoRequest,
19
19
  VellumValue,
20
20
  VellumValueRequest,
21
21
  VellumVariableType,
22
+ VellumVideo,
23
+ VellumVideoRequest,
22
24
  )
23
25
  from vellum.workflows.descriptors.base import BaseDescriptor
24
26
  from vellum.workflows.types.core import Json
@@ -67,8 +69,8 @@ def primitive_type_to_vellum_variable_type(type_: Union[Type, BaseDescriptor]) -
67
69
  return "FUNCTION_CALL"
68
70
  elif _is_type_optionally_in(type_, (VellumAudio, VellumAudioRequest)):
69
71
  return "AUDIO"
70
- # elif _is_type_optionally_in(type_, (VellumVideo, VellumVideoRequest)):
71
- # return "VIDEO"
72
+ elif _is_type_optionally_in(type_, (VellumVideo, VellumVideoRequest)):
73
+ return "VIDEO"
72
74
  elif _is_type_optionally_in(type_, (VellumImage, VellumImageRequest)):
73
75
  return "IMAGE"
74
76
  elif _is_type_optionally_in(type_, (VellumDocument, VellumDocumentRequest)):
@@ -211,6 +211,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
211
211
  unused_graphs: ClassVar[Set[GraphAttribute]] # nodes or graphs that are defined but not used in the graph
212
212
  emitters: List[BaseWorkflowEmitter]
213
213
  resolvers: List[BaseWorkflowResolver]
214
+ is_dynamic: ClassVar[bool] = False
214
215
 
215
216
  class Outputs(BaseOutputs):
216
217
  pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0