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.
- vellum/__init__.py +40 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/core/pydantic_utilities.py +3 -2
- vellum/client/reference.md +16 -0
- vellum/client/resources/workflow_executions/client.py +28 -4
- vellum/client/resources/workflow_executions/raw_client.py +32 -2
- vellum/client/types/__init__.py +40 -0
- vellum/client/types/audio_input_request.py +30 -0
- vellum/client/types/delimiter_chunker_config.py +20 -0
- vellum/client/types/delimiter_chunker_config_request.py +20 -0
- vellum/client/types/delimiter_chunking.py +21 -0
- vellum/client/types/delimiter_chunking_request.py +21 -0
- vellum/client/types/document_index_chunking.py +4 -1
- vellum/client/types/document_index_chunking_request.py +2 -1
- vellum/client/types/document_input_request.py +30 -0
- vellum/client/types/execution_audio_vellum_value.py +31 -0
- vellum/client/types/execution_document_vellum_value.py +31 -0
- vellum/client/types/execution_image_vellum_value.py +31 -0
- vellum/client/types/execution_vellum_value.py +8 -0
- vellum/client/types/execution_video_vellum_value.py +31 -0
- vellum/client/types/image_input_request.py +30 -0
- vellum/client/types/logical_operator.py +1 -0
- vellum/client/types/node_input_compiled_audio_value.py +23 -0
- vellum/client/types/node_input_compiled_document_value.py +23 -0
- vellum/client/types/node_input_compiled_image_value.py +23 -0
- vellum/client/types/node_input_compiled_video_value.py +23 -0
- vellum/client/types/node_input_variable_compiled_value.py +8 -0
- vellum/client/types/prompt_deployment_input_request.py +13 -1
- vellum/client/types/prompt_request_audio_input.py +26 -0
- vellum/client/types/prompt_request_document_input.py +26 -0
- vellum/client/types/prompt_request_image_input.py +26 -0
- vellum/client/types/prompt_request_input.py +13 -1
- vellum/client/types/prompt_request_video_input.py +26 -0
- vellum/client/types/video_input_request.py +30 -0
- vellum/prompts/blocks/compilation.py +13 -11
- vellum/types/audio_input_request.py +3 -0
- vellum/types/delimiter_chunker_config.py +3 -0
- vellum/types/delimiter_chunker_config_request.py +3 -0
- vellum/types/delimiter_chunking.py +3 -0
- vellum/types/delimiter_chunking_request.py +3 -0
- vellum/types/document_input_request.py +3 -0
- vellum/types/execution_audio_vellum_value.py +3 -0
- vellum/types/execution_document_vellum_value.py +3 -0
- vellum/types/execution_image_vellum_value.py +3 -0
- vellum/types/execution_video_vellum_value.py +3 -0
- vellum/types/image_input_request.py +3 -0
- vellum/types/node_input_compiled_audio_value.py +3 -0
- vellum/types/node_input_compiled_document_value.py +3 -0
- vellum/types/node_input_compiled_image_value.py +3 -0
- vellum/types/node_input_compiled_video_value.py +3 -0
- vellum/types/prompt_request_audio_input.py +3 -0
- vellum/types/prompt_request_document_input.py +3 -0
- vellum/types/prompt_request_image_input.py +3 -0
- vellum/types/prompt_request_video_input.py +3 -0
- vellum/types/video_input_request.py +3 -0
- vellum/workflows/context.py +27 -9
- vellum/workflows/emitters/vellum_emitter.py +16 -69
- vellum/workflows/events/context.py +53 -78
- vellum/workflows/events/node.py +5 -5
- vellum/workflows/events/relational_threads.py +41 -0
- vellum/workflows/events/tests/test_basic_workflow.py +50 -0
- vellum/workflows/events/tests/test_event.py +1 -0
- vellum/workflows/events/workflow.py +15 -1
- vellum/workflows/expressions/contains.py +7 -0
- vellum/workflows/expressions/tests/test_contains.py +175 -0
- vellum/workflows/graph/graph.py +52 -8
- vellum/workflows/graph/tests/test_graph.py +17 -0
- vellum/workflows/integrations/mcp_service.py +35 -5
- vellum/workflows/integrations/tests/test_mcp_service.py +81 -0
- vellum/workflows/nodes/bases/base.py +0 -1
- vellum/workflows/nodes/core/error_node/node.py +4 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +35 -0
- vellum/workflows/nodes/core/map_node/node.py +7 -0
- vellum/workflows/nodes/core/map_node/tests/test_node.py +19 -0
- vellum/workflows/nodes/displayable/bases/utils.py +4 -2
- vellum/workflows/nodes/displayable/final_output_node/node.py +4 -0
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +88 -2
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +1 -0
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +85 -1
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py +12 -0
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +5 -2
- vellum/workflows/ports/node_ports.py +3 -0
- vellum/workflows/ports/port.py +8 -11
- vellum/workflows/state/context.py +47 -2
- vellum/workflows/types/definition.py +4 -4
- vellum/workflows/utils/uuids.py +15 -0
- vellum/workflows/utils/vellum_variables.py +5 -3
- vellum/workflows/workflows/base.py +1 -0
- {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/METADATA +1 -1
- {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/RECORD +128 -82
- vellum_ee/workflows/display/nodes/base_node_display.py +19 -10
- vellum_ee/workflows/display/nodes/vellum/api_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/error_node.py +1 -3
- vellum_ee/workflows/display/nodes/vellum/final_output_node.py +1 -3
- vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +1 -8
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/merge_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/note_node.py +2 -4
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +1 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +12 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +16 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +5 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +12 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +5 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +1 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +5 -0
- vellum_ee/workflows/display/utils/events.py +24 -0
- vellum_ee/workflows/display/utils/tests/test_events.py +69 -0
- vellum_ee/workflows/tests/test_server.py +95 -0
- {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/LICENSE +0 -0
- {vellum_ai-1.2.1.dist-info → vellum_ai-1.2.3.dist-info}/WHEEL +0 -0
- {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
|
-
|
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
|
-
|
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:
|
vellum/workflows/ports/port.py
CHANGED
@@ -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
|
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(
|
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
|
-
|
116
|
-
|
117
|
-
|
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):
|
vellum/workflows/utils/uuids.py
CHANGED
@@ -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 (
|
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
|
-
|
71
|
-
|
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
|