vellum-ai 1.11.2__py3-none-any.whl → 1.13.5__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.
Potentially problematic release.
This version of vellum-ai might be problematic. Click here for more details.
- vellum/__init__.py +18 -0
- vellum/client/README.md +1 -1
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/core/force_multipart.py +4 -2
- vellum/client/core/http_response.py +1 -1
- vellum/client/core/pydantic_utilities.py +7 -4
- vellum/client/errors/too_many_requests_error.py +1 -2
- vellum/client/reference.md +677 -76
- vellum/client/resources/container_images/client.py +299 -0
- vellum/client/resources/container_images/raw_client.py +286 -0
- vellum/client/resources/documents/client.py +20 -10
- vellum/client/resources/documents/raw_client.py +20 -10
- vellum/client/resources/events/raw_client.py +4 -4
- vellum/client/resources/integration_auth_configs/client.py +2 -0
- vellum/client/resources/integration_auth_configs/raw_client.py +2 -0
- vellum/client/resources/integration_providers/client.py +28 -2
- vellum/client/resources/integration_providers/raw_client.py +24 -0
- vellum/client/resources/integrations/client.py +52 -4
- vellum/client/resources/integrations/raw_client.py +61 -0
- vellum/client/resources/workflow_deployments/client.py +156 -0
- vellum/client/resources/workflow_deployments/raw_client.py +334 -0
- vellum/client/resources/workflows/client.py +212 -8
- vellum/client/resources/workflows/raw_client.py +343 -6
- vellum/client/types/__init__.py +18 -0
- vellum/client/types/api_actor_type_enum.py +1 -1
- vellum/client/types/check_workflow_execution_status_error.py +21 -0
- vellum/client/types/check_workflow_execution_status_response.py +29 -0
- vellum/client/types/code_execution_package_request.py +21 -0
- vellum/client/types/composio_execute_tool_request.py +5 -0
- vellum/client/types/composio_tool_definition.py +1 -0
- vellum/client/types/container_image_build_config.py +1 -0
- vellum/client/types/container_image_container_image_tag.py +1 -0
- vellum/client/types/dataset_row_push_request.py +3 -0
- vellum/client/types/document_document_to_document_index.py +1 -0
- vellum/client/types/integration_name.py +24 -0
- vellum/client/types/node_execution_fulfilled_body.py +1 -0
- vellum/client/types/node_execution_log_body.py +24 -0
- vellum/client/types/node_execution_log_event.py +47 -0
- vellum/client/types/prompt_deployment_release_prompt_deployment.py +1 -0
- vellum/client/types/runner_config_request.py +24 -0
- vellum/client/types/severity_enum.py +5 -0
- vellum/client/types/slim_composio_tool_definition.py +1 -0
- vellum/client/types/slim_document_document_to_document_index.py +2 -0
- vellum/client/types/type_checker_enum.py +5 -0
- vellum/client/types/vellum_audio.py +5 -1
- vellum/client/types/vellum_audio_request.py +5 -1
- vellum/client/types/vellum_document.py +5 -1
- vellum/client/types/vellum_document_request.py +5 -1
- vellum/client/types/vellum_image.py +5 -1
- vellum/client/types/vellum_image_request.py +5 -1
- vellum/client/types/vellum_node_execution_event.py +2 -0
- vellum/client/types/vellum_variable.py +5 -0
- vellum/client/types/vellum_variable_extensions.py +1 -0
- vellum/client/types/vellum_variable_type.py +1 -0
- vellum/client/types/vellum_video.py +5 -1
- vellum/client/types/vellum_video_request.py +5 -1
- vellum/client/types/workflow_deployment_release_workflow_deployment.py +1 -0
- vellum/client/types/workflow_event.py +2 -0
- vellum/client/types/workflow_execution_fulfilled_body.py +1 -0
- vellum/client/types/workflow_result_event_output_data_array.py +1 -1
- vellum/client/types/workflow_result_event_output_data_chat_history.py +1 -1
- vellum/client/types/workflow_result_event_output_data_error.py +1 -1
- vellum/client/types/workflow_result_event_output_data_function_call.py +1 -1
- vellum/client/types/workflow_result_event_output_data_json.py +1 -1
- vellum/client/types/workflow_result_event_output_data_number.py +1 -1
- vellum/client/types/workflow_result_event_output_data_search_results.py +1 -1
- vellum/client/types/workflow_result_event_output_data_string.py +1 -1
- vellum/client/types/workflow_sandbox_execute_node_response.py +8 -0
- vellum/plugins/vellum_mypy.py +37 -2
- vellum/types/check_workflow_execution_status_error.py +3 -0
- vellum/types/check_workflow_execution_status_response.py +3 -0
- vellum/types/code_execution_package_request.py +3 -0
- vellum/types/node_execution_log_body.py +3 -0
- vellum/types/node_execution_log_event.py +3 -0
- vellum/types/runner_config_request.py +3 -0
- vellum/types/severity_enum.py +3 -0
- vellum/types/type_checker_enum.py +3 -0
- vellum/types/workflow_sandbox_execute_node_response.py +3 -0
- vellum/utils/files/mixin.py +26 -0
- vellum/utils/files/tests/test_mixin.py +62 -0
- vellum/utils/tests/test_vellum_client.py +95 -0
- vellum/utils/uuid.py +19 -2
- vellum/utils/vellum_client.py +10 -3
- vellum/workflows/__init__.py +7 -1
- vellum/workflows/descriptors/base.py +86 -0
- vellum/workflows/descriptors/tests/test_utils.py +9 -0
- vellum/workflows/errors/tests/__init__.py +0 -0
- vellum/workflows/errors/tests/test_types.py +52 -0
- vellum/workflows/errors/types.py +1 -0
- vellum/workflows/events/node.py +24 -0
- vellum/workflows/events/tests/test_event.py +123 -0
- vellum/workflows/events/types.py +2 -1
- vellum/workflows/events/workflow.py +28 -2
- vellum/workflows/expressions/add.py +3 -0
- vellum/workflows/expressions/tests/test_add.py +24 -0
- vellum/workflows/graph/graph.py +26 -5
- vellum/workflows/graph/tests/test_graph.py +228 -1
- vellum/workflows/inputs/base.py +22 -6
- vellum/workflows/inputs/dataset_row.py +121 -16
- vellum/workflows/inputs/tests/test_inputs.py +3 -3
- vellum/workflows/integrations/tests/test_vellum_integration_service.py +84 -0
- vellum/workflows/integrations/vellum_integration_service.py +12 -1
- vellum/workflows/loaders/base.py +2 -0
- vellum/workflows/nodes/bases/base.py +37 -16
- vellum/workflows/nodes/bases/tests/test_base_node.py +104 -1
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +1 -1
- vellum/workflows/nodes/core/map_node/node.py +7 -5
- vellum/workflows/nodes/core/map_node/tests/test_node.py +33 -0
- vellum/workflows/nodes/core/retry_node/node.py +1 -0
- vellum/workflows/nodes/core/try_node/node.py +1 -0
- vellum/workflows/nodes/displayable/api_node/node.py +3 -2
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +38 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +18 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +109 -2
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +13 -2
- vellum/workflows/nodes/displayable/code_execution_node/node.py +9 -15
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py +65 -24
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -0
- vellum/workflows/nodes/displayable/final_output_node/node.py +24 -69
- vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +53 -3
- vellum/workflows/nodes/displayable/note_node/node.py +4 -1
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +16 -5
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +47 -0
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +74 -34
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +204 -8
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +92 -71
- vellum/workflows/nodes/mocks.py +47 -213
- vellum/workflows/nodes/tests/test_mocks.py +0 -177
- vellum/workflows/nodes/utils.py +23 -8
- vellum/workflows/outputs/base.py +36 -3
- vellum/workflows/references/environment_variable.py +1 -11
- vellum/workflows/references/lazy.py +8 -0
- vellum/workflows/references/state_value.py +24 -1
- vellum/workflows/references/tests/test_lazy.py +58 -0
- vellum/workflows/references/trigger.py +8 -3
- vellum/workflows/references/workflow_input.py +8 -0
- vellum/workflows/resolvers/resolver.py +13 -3
- vellum/workflows/resolvers/tests/test_resolver.py +31 -0
- vellum/workflows/runner/runner.py +159 -14
- vellum/workflows/runner/tests/__init__.py +0 -0
- vellum/workflows/runner/tests/test_runner.py +170 -0
- vellum/workflows/sandbox.py +7 -8
- vellum/workflows/state/base.py +89 -30
- vellum/workflows/state/context.py +74 -3
- vellum/workflows/state/tests/test_state.py +269 -1
- vellum/workflows/tests/test_dataset_row.py +8 -7
- vellum/workflows/tests/test_sandbox.py +97 -8
- vellum/workflows/triggers/__init__.py +2 -1
- vellum/workflows/triggers/base.py +160 -28
- vellum/workflows/triggers/chat_message.py +141 -0
- vellum/workflows/triggers/integration.py +12 -0
- vellum/workflows/triggers/manual.py +3 -1
- vellum/workflows/triggers/schedule.py +3 -1
- vellum/workflows/triggers/tests/test_chat_message.py +257 -0
- vellum/workflows/types/core.py +18 -0
- vellum/workflows/types/definition.py +6 -13
- vellum/workflows/types/generics.py +12 -0
- vellum/workflows/types/tests/test_utils.py +12 -0
- vellum/workflows/types/utils.py +32 -2
- vellum/workflows/types/workflow_metadata.py +124 -0
- vellum/workflows/utils/functions.py +152 -16
- vellum/workflows/utils/pydantic_schema.py +19 -1
- vellum/workflows/utils/tests/test_functions.py +123 -8
- vellum/workflows/utils/tests/test_validate.py +79 -0
- vellum/workflows/utils/tests/test_vellum_variables.py +62 -2
- vellum/workflows/utils/uuids.py +90 -0
- vellum/workflows/utils/validate.py +108 -0
- vellum/workflows/utils/vellum_variables.py +96 -16
- vellum/workflows/workflows/base.py +177 -35
- vellum/workflows/workflows/tests/test_base_workflow.py +51 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/METADATA +6 -1
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/RECORD +274 -227
- vellum_cli/__init__.py +21 -0
- vellum_cli/config.py +16 -2
- vellum_cli/pull.py +2 -0
- vellum_cli/push.py +23 -10
- vellum_cli/tests/conftest.py +8 -13
- vellum_cli/tests/test_image_push.py +4 -11
- vellum_cli/tests/test_pull.py +83 -68
- vellum_cli/tests/test_push.py +251 -2
- vellum_ee/assets/node-definitions.json +225 -12
- vellum_ee/scripts/generate_node_definitions.py +15 -3
- vellum_ee/workflows/display/base.py +4 -3
- vellum_ee/workflows/display/nodes/base_node_display.py +44 -11
- vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +93 -0
- vellum_ee/workflows/display/nodes/types.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/__init__.py +0 -2
- vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +5 -2
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +10 -2
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +17 -14
- vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/note_node.py +18 -3
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +37 -14
- vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +62 -2
- vellum_ee/workflows/display/nodes/vellum/tests/test_final_output_node.py +136 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_note_node.py +44 -7
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +5 -13
- vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +27 -17
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +145 -22
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +107 -2
- vellum_ee/workflows/display/nodes/vellum/utils.py +54 -12
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +13 -16
- vellum_ee/workflows/display/tests/test_json_schema_validation.py +190 -0
- vellum_ee/workflows/display/tests/test_mocks.py +912 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +14 -2
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +109 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +187 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +34 -325
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +42 -393
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +13 -315
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +2 -122
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +24 -115
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +4 -93
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +7 -80
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +9 -101
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +77 -308
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +62 -324
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -82
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +4 -142
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +1 -61
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_set_state_node_serialization.py +4 -4
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +205 -134
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +34 -146
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +2 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +8 -6
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +137 -266
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_tool_wrapper_serialization.py +84 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +55 -16
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +15 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_tool_wrapper_serialization.py +71 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_vellum_integration_serialization.py +119 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +0 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_dict_reference_serialization.py +22 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_trigger_serialization.py +412 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_code_tool_node_reference_error.py +106 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +9 -41
- vellum_ee/workflows/display/tests/workflow_serialization/test_duplicate_trigger_name_validation.py +208 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_not_referenced_by_workflow_outputs.py +45 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_infinite_loop_validation.py +66 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_int_input_serialization.py +40 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_serialization.py +8 -14
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_validation.py +173 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_with_entrypoint_node_id.py +16 -13
- vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py +5 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py +12 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_multi_trigger_same_node_serialization.py +111 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_no_triggers_no_entrypoint_validation.py +64 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_partial_workflow_meta_display_override.py +55 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_dataset_mocks_serialization.py +268 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_invalid_pdf_data_url.py +49 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_validation_errors.py +112 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_scheduled_trigger_serialization.py +25 -16
- vellum_ee/workflows/display/tests/workflow_serialization/test_terminal_node_in_unused_graphs_serialization.py +53 -0
- vellum_ee/workflows/display/utils/exceptions.py +34 -0
- vellum_ee/workflows/display/utils/expressions.py +463 -52
- vellum_ee/workflows/display/utils/metadata.py +98 -33
- vellum_ee/workflows/display/utils/tests/test_metadata.py +31 -0
- vellum_ee/workflows/display/utils/triggers.py +153 -0
- vellum_ee/workflows/display/utils/vellum.py +59 -5
- vellum_ee/workflows/display/workflows/base_workflow_display.py +656 -254
- vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +26 -0
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +77 -29
- vellum_ee/workflows/server/namespaces.py +18 -0
- vellum_ee/workflows/tests/test_display_meta.py +2 -0
- vellum_ee/workflows/tests/test_serialize_module.py +174 -7
- vellum_ee/workflows/tests/test_server.py +0 -3
- vellum_ee/workflows/display/nodes/vellum/function_node.py +0 -14
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/LICENSE +0 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/WHEEL +0 -0
- {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/entry_points.txt +0 -0
vellum_ee/workflows/display/tests/workflow_serialization/test_code_tool_node_reference_error.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from vellum.workflows import BaseWorkflow
|
|
4
|
+
from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
|
|
5
|
+
from vellum.workflows.nodes.displayable.tool_calling_node.node import ToolCallingNode
|
|
6
|
+
from vellum_ee.workflows.display.utils.exceptions import UnsupportedSerializationException
|
|
7
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_serialize_workflow__code_tool_with_node_reference__raises_error():
|
|
11
|
+
"""
|
|
12
|
+
Tests that a serialization error is raised when a code tool references a workflow node.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# GIVEN a function that references a node class
|
|
16
|
+
def my_tool_with_node_reference(query: str) -> str:
|
|
17
|
+
return str(InlinePromptNode)
|
|
18
|
+
|
|
19
|
+
# AND a tool calling node that uses this function
|
|
20
|
+
class MyToolCallingNode(ToolCallingNode):
|
|
21
|
+
functions = [my_tool_with_node_reference]
|
|
22
|
+
|
|
23
|
+
# AND a workflow with the tool calling node
|
|
24
|
+
class TestWorkflow(BaseWorkflow):
|
|
25
|
+
graph = MyToolCallingNode
|
|
26
|
+
|
|
27
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
28
|
+
result = MyToolCallingNode.Outputs.text
|
|
29
|
+
|
|
30
|
+
# WHEN we try to serialize the workflow
|
|
31
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
32
|
+
|
|
33
|
+
# THEN a serialization error should be raised
|
|
34
|
+
with pytest.raises(UnsupportedSerializationException) as exc_info:
|
|
35
|
+
workflow_display.serialize()
|
|
36
|
+
|
|
37
|
+
# AND the error message should mention the node class and provide guidance
|
|
38
|
+
error_message = str(exc_info.value)
|
|
39
|
+
assert "InlinePromptNode" in error_message
|
|
40
|
+
assert "Code tools cannot reference workflow nodes" in error_message
|
|
41
|
+
assert "Inline Subworkflow tool" in error_message
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_serialize_workflow__code_tool_without_node_reference__no_error():
|
|
45
|
+
"""
|
|
46
|
+
Tests that no error is raised when a code tool does not reference any workflow nodes,
|
|
47
|
+
even if the module has node imports at the top level.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# GIVEN a function that does NOT reference any node class (even though InlinePromptNode
|
|
51
|
+
# is imported at the module level)
|
|
52
|
+
def my_simple_tool(query: str) -> str:
|
|
53
|
+
return query.upper()
|
|
54
|
+
|
|
55
|
+
# AND a tool calling node that uses this function
|
|
56
|
+
class MyToolCallingNode(ToolCallingNode):
|
|
57
|
+
functions = [my_simple_tool]
|
|
58
|
+
|
|
59
|
+
# AND a workflow with the tool calling node
|
|
60
|
+
class TestWorkflow(BaseWorkflow):
|
|
61
|
+
graph = MyToolCallingNode
|
|
62
|
+
|
|
63
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
64
|
+
result = MyToolCallingNode.Outputs.text
|
|
65
|
+
|
|
66
|
+
# WHEN we serialize the workflow
|
|
67
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
68
|
+
serialized_workflow = workflow_display.serialize()
|
|
69
|
+
|
|
70
|
+
# THEN no error should be raised and the workflow should serialize successfully
|
|
71
|
+
assert serialized_workflow is not None
|
|
72
|
+
assert "workflow_raw_data" in serialized_workflow
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_serialize_workflow__code_tool_with_node_instantiation__raises_error():
|
|
76
|
+
"""
|
|
77
|
+
Tests that a serialization error is raised when a code tool instantiates a node and calls its run method.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
# GIVEN a function that instantiates a node and calls its run method
|
|
81
|
+
def my_tool():
|
|
82
|
+
node: InlinePromptNode = InlinePromptNode()
|
|
83
|
+
return node.run()
|
|
84
|
+
|
|
85
|
+
# AND a tool calling node that uses this function
|
|
86
|
+
class MyToolCallingNode(ToolCallingNode):
|
|
87
|
+
functions = [my_tool]
|
|
88
|
+
|
|
89
|
+
# AND a workflow with the tool calling node
|
|
90
|
+
class TestWorkflow(BaseWorkflow):
|
|
91
|
+
graph = MyToolCallingNode
|
|
92
|
+
|
|
93
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
94
|
+
result = MyToolCallingNode.Outputs.text
|
|
95
|
+
|
|
96
|
+
# WHEN we try to serialize the workflow
|
|
97
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
98
|
+
|
|
99
|
+
# THEN a serialization error should be raised
|
|
100
|
+
with pytest.raises(UnsupportedSerializationException) as exc_info:
|
|
101
|
+
workflow_display.serialize()
|
|
102
|
+
|
|
103
|
+
# AND the error message should mention the node class
|
|
104
|
+
error_message = str(exc_info.value)
|
|
105
|
+
assert "InlinePromptNode" in error_message
|
|
106
|
+
assert "Code tools cannot reference workflow nodes" in error_message
|
vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py
CHANGED
|
@@ -2,6 +2,7 @@ import pytest
|
|
|
2
2
|
|
|
3
3
|
from deepdiff import DeepDiff
|
|
4
4
|
|
|
5
|
+
from vellum_ee.workflows.display.utils.exceptions import WorkflowValidationError
|
|
5
6
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
6
7
|
|
|
7
8
|
from tests.workflows.complex_final_output_node.missing_final_output_node import MissingFinalOutputNodeWorkflow
|
|
@@ -31,7 +32,7 @@ def test_serialize_workflow__missing_final_output_node():
|
|
|
31
32
|
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
|
32
33
|
assert isinstance(workflow_raw_data, dict)
|
|
33
34
|
|
|
34
|
-
# AND we should create synthetic terminal nodes for each output variable
|
|
35
|
+
# AND we should NOT create synthetic terminal nodes for each output variable
|
|
35
36
|
final_output_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "TERMINAL"]
|
|
36
37
|
assert not DeepDiff(
|
|
37
38
|
[
|
|
@@ -79,6 +80,7 @@ def test_serialize_workflow__missing_final_output_node():
|
|
|
79
80
|
"type": "WORKFLOW_INPUT",
|
|
80
81
|
"input_variable_id": "da086239-d743-4246-b666-5c91e22fb88c",
|
|
81
82
|
},
|
|
83
|
+
"schema": {"type": "string"},
|
|
82
84
|
}
|
|
83
85
|
],
|
|
84
86
|
"trigger": {
|
|
@@ -87,45 +89,8 @@ def test_serialize_workflow__missing_final_output_node():
|
|
|
87
89
|
},
|
|
88
90
|
"ports": [],
|
|
89
91
|
},
|
|
90
|
-
{
|
|
91
|
-
"id": "bb88768d-472e-4997-b7ea-de09163d1b4c",
|
|
92
|
-
"type": "TERMINAL",
|
|
93
|
-
"data": {
|
|
94
|
-
"label": "Final Output",
|
|
95
|
-
"name": "beta",
|
|
96
|
-
"target_handle_id": "5e337b19-cef6-45af-802b-46da4ad7e794",
|
|
97
|
-
"output_id": "5e6d3ea6-ef91-4937-8fff-f33e07446e6a",
|
|
98
|
-
"output_type": "STRING",
|
|
99
|
-
"node_input_id": "590161f1-20ed-4339-9ce3-61aade3a142a",
|
|
100
|
-
},
|
|
101
|
-
"inputs": [
|
|
102
|
-
{
|
|
103
|
-
"id": "590161f1-20ed-4339-9ce3-61aade3a142a",
|
|
104
|
-
"key": "node_input",
|
|
105
|
-
"value": {
|
|
106
|
-
"rules": [
|
|
107
|
-
{
|
|
108
|
-
"type": "NODE_OUTPUT",
|
|
109
|
-
"data": {
|
|
110
|
-
"node_id": "74b891e0-5573-4c7a-a0ef-c3c210187738",
|
|
111
|
-
"output_id": "ae21fc3f-dd81-4174-b08a-97bc96422a8f",
|
|
112
|
-
},
|
|
113
|
-
}
|
|
114
|
-
],
|
|
115
|
-
"combinator": "OR",
|
|
116
|
-
},
|
|
117
|
-
}
|
|
118
|
-
],
|
|
119
|
-
"display_data": {"position": {"x": 400.0, "y": -50.0}},
|
|
120
|
-
"base": {
|
|
121
|
-
"name": "FinalOutputNode",
|
|
122
|
-
"module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
|
|
123
|
-
},
|
|
124
|
-
"definition": None,
|
|
125
|
-
},
|
|
126
92
|
],
|
|
127
93
|
final_output_nodes,
|
|
128
|
-
ignore_order=True,
|
|
129
94
|
)
|
|
130
95
|
|
|
131
96
|
|
|
@@ -133,8 +98,11 @@ def test_serialize_workflow__missing_workflow_output():
|
|
|
133
98
|
# GIVEN a Workflow that contains a terminal node that is unreferenced by the Workflow's Outputs
|
|
134
99
|
workflow_display = get_workflow_display(workflow_class=MissingWorkflowOutputWorkflow)
|
|
135
100
|
|
|
136
|
-
# WHEN we serialize it, it should throw
|
|
137
|
-
with pytest.raises(
|
|
101
|
+
# WHEN we serialize it, it should throw a WorkflowValidationError
|
|
102
|
+
with pytest.raises(WorkflowValidationError) as exc_info:
|
|
138
103
|
workflow_display.serialize()
|
|
139
104
|
|
|
140
|
-
|
|
105
|
+
# THEN the error message should indicate the terminal node is not referenced
|
|
106
|
+
error_message = str(exc_info.value)
|
|
107
|
+
assert "MissingWorkflowOutputWorkflow" in error_message
|
|
108
|
+
assert "terminal nodes that are not referenced by workflow outputs" in error_message
|
vellum_ee/workflows/display/tests/workflow_serialization/test_duplicate_trigger_name_validation.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Tests for duplicate trigger name validation during serialization."""
|
|
2
|
+
|
|
3
|
+
from vellum.workflows import BaseWorkflow
|
|
4
|
+
from vellum.workflows.inputs.base import BaseInputs
|
|
5
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
|
6
|
+
from vellum.workflows.state.base import BaseState
|
|
7
|
+
from vellum.workflows.triggers.integration import IntegrationTrigger
|
|
8
|
+
from vellum_ee.workflows.display.utils.exceptions import TriggerValidationError
|
|
9
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_duplicate_trigger_names__same_slug_produces_error():
|
|
13
|
+
"""
|
|
14
|
+
Tests that two integration triggers with the same slug produce a validation error.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# GIVEN two IntegrationTrigger subclasses with the same slug
|
|
18
|
+
class SlackTrigger1(IntegrationTrigger):
|
|
19
|
+
message: str
|
|
20
|
+
|
|
21
|
+
class Config:
|
|
22
|
+
provider = "COMPOSIO"
|
|
23
|
+
integration_name = "SLACK"
|
|
24
|
+
slug = "slack_new_message"
|
|
25
|
+
|
|
26
|
+
class SlackTrigger2(IntegrationTrigger):
|
|
27
|
+
channel: str
|
|
28
|
+
|
|
29
|
+
class Config:
|
|
30
|
+
provider = "COMPOSIO"
|
|
31
|
+
integration_name = "SLACK"
|
|
32
|
+
slug = "slack_new_message"
|
|
33
|
+
|
|
34
|
+
# AND nodes for each trigger
|
|
35
|
+
class ProcessNode1(BaseNode):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
class ProcessNode2(BaseNode):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
# AND a workflow with both triggers
|
|
42
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
43
|
+
graph = {
|
|
44
|
+
SlackTrigger1 >> ProcessNode1,
|
|
45
|
+
SlackTrigger2 >> ProcessNode2,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# WHEN we serialize the workflow
|
|
49
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
50
|
+
workflow_display.serialize()
|
|
51
|
+
|
|
52
|
+
# THEN the display_context should contain a TriggerValidationError
|
|
53
|
+
errors = list(workflow_display.display_context.errors)
|
|
54
|
+
trigger_errors = [e for e in errors if isinstance(e, TriggerValidationError)]
|
|
55
|
+
assert len(trigger_errors) == 1
|
|
56
|
+
|
|
57
|
+
# AND the error should mention duplicate trigger name
|
|
58
|
+
error = trigger_errors[0]
|
|
59
|
+
assert "Duplicate trigger name" in str(error)
|
|
60
|
+
assert "slack_new_message" in str(error)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_different_trigger_names__no_error():
|
|
64
|
+
"""
|
|
65
|
+
Tests that two integration triggers with different slugs do not produce a validation error.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# GIVEN two IntegrationTrigger subclasses with different slugs
|
|
69
|
+
class SlackTrigger(IntegrationTrigger):
|
|
70
|
+
message: str
|
|
71
|
+
|
|
72
|
+
class Config:
|
|
73
|
+
provider = "COMPOSIO"
|
|
74
|
+
integration_name = "SLACK"
|
|
75
|
+
slug = "slack_new_message"
|
|
76
|
+
|
|
77
|
+
class GmailTrigger(IntegrationTrigger):
|
|
78
|
+
email: str
|
|
79
|
+
|
|
80
|
+
class Config:
|
|
81
|
+
provider = "COMPOSIO"
|
|
82
|
+
integration_name = "GMAIL"
|
|
83
|
+
slug = "gmail_new_email"
|
|
84
|
+
|
|
85
|
+
# AND nodes for each trigger
|
|
86
|
+
class ProcessSlack(BaseNode):
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
class ProcessGmail(BaseNode):
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
# AND a workflow with both triggers
|
|
93
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
94
|
+
graph = {
|
|
95
|
+
SlackTrigger >> ProcessSlack,
|
|
96
|
+
GmailTrigger >> ProcessGmail,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# WHEN we serialize the workflow
|
|
100
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
101
|
+
result: dict = workflow_display.serialize()
|
|
102
|
+
|
|
103
|
+
# THEN there should be no TriggerValidationError about duplicate names
|
|
104
|
+
errors = list(workflow_display.display_context.errors)
|
|
105
|
+
duplicate_errors = [
|
|
106
|
+
e for e in errors if isinstance(e, TriggerValidationError) and "Duplicate trigger name" in str(e)
|
|
107
|
+
]
|
|
108
|
+
assert len(duplicate_errors) == 0
|
|
109
|
+
|
|
110
|
+
# AND both triggers should be serialized with their respective names
|
|
111
|
+
triggers = result["triggers"]
|
|
112
|
+
assert isinstance(triggers, list)
|
|
113
|
+
assert len(triggers) == 2
|
|
114
|
+
trigger_names = {t["name"] for t in triggers}
|
|
115
|
+
assert trigger_names == {"slack_new_message", "gmail_new_email"}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_trigger_name_serialization__chat_trigger():
|
|
119
|
+
"""
|
|
120
|
+
Tests that ChatMessageTrigger serializes with name 'chat'.
|
|
121
|
+
"""
|
|
122
|
+
from vellum.workflows.triggers.chat_message import ChatMessageTrigger
|
|
123
|
+
|
|
124
|
+
# GIVEN a ChatMessageTrigger subclass
|
|
125
|
+
class MyChatTrigger(ChatMessageTrigger):
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
# AND a simple node
|
|
129
|
+
class ProcessNode(BaseNode):
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
# AND a workflow with the chat trigger
|
|
133
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
134
|
+
graph = MyChatTrigger >> ProcessNode
|
|
135
|
+
|
|
136
|
+
# WHEN we serialize the workflow
|
|
137
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
138
|
+
result: dict = workflow_display.serialize()
|
|
139
|
+
|
|
140
|
+
# THEN the trigger should have name 'chat'
|
|
141
|
+
triggers = result["triggers"]
|
|
142
|
+
assert isinstance(triggers, list)
|
|
143
|
+
assert len(triggers) == 1
|
|
144
|
+
assert triggers[0]["name"] == "chat"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_trigger_name_serialization__scheduled_trigger():
|
|
148
|
+
"""
|
|
149
|
+
Tests that ScheduleTrigger serializes with name 'scheduled'.
|
|
150
|
+
"""
|
|
151
|
+
from vellum.workflows.triggers.schedule import ScheduleTrigger
|
|
152
|
+
|
|
153
|
+
# GIVEN a ScheduleTrigger subclass
|
|
154
|
+
class MyScheduledTrigger(ScheduleTrigger):
|
|
155
|
+
class Config(ScheduleTrigger.Config):
|
|
156
|
+
cron = "0 9 * * *"
|
|
157
|
+
timezone = "UTC"
|
|
158
|
+
|
|
159
|
+
# AND a simple node
|
|
160
|
+
class ProcessNode(BaseNode):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
# AND a workflow with the scheduled trigger
|
|
164
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
165
|
+
graph = MyScheduledTrigger >> ProcessNode
|
|
166
|
+
|
|
167
|
+
# WHEN we serialize the workflow
|
|
168
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
169
|
+
result: dict = workflow_display.serialize()
|
|
170
|
+
|
|
171
|
+
# THEN the trigger should have name 'scheduled'
|
|
172
|
+
triggers = result["triggers"]
|
|
173
|
+
assert isinstance(triggers, list)
|
|
174
|
+
assert len(triggers) == 1
|
|
175
|
+
assert triggers[0]["name"] == "scheduled"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_trigger_name_serialization__integration_trigger():
|
|
179
|
+
"""
|
|
180
|
+
Tests that IntegrationTrigger serializes with name equal to its slug.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
# GIVEN an IntegrationTrigger subclass with a specific slug
|
|
184
|
+
class MySlackTrigger(IntegrationTrigger):
|
|
185
|
+
message: str
|
|
186
|
+
|
|
187
|
+
class Config:
|
|
188
|
+
provider = "COMPOSIO"
|
|
189
|
+
integration_name = "SLACK"
|
|
190
|
+
slug = "slack_new_message"
|
|
191
|
+
|
|
192
|
+
# AND a simple node
|
|
193
|
+
class ProcessNode(BaseNode):
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
# AND a workflow with the integration trigger
|
|
197
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
198
|
+
graph = MySlackTrigger >> ProcessNode
|
|
199
|
+
|
|
200
|
+
# WHEN we serialize the workflow
|
|
201
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
202
|
+
result: dict = workflow_display.serialize()
|
|
203
|
+
|
|
204
|
+
# THEN the trigger should have name equal to the slug
|
|
205
|
+
triggers = result["triggers"]
|
|
206
|
+
assert isinstance(triggers, list)
|
|
207
|
+
assert len(triggers) == 1
|
|
208
|
+
assert triggers[0]["name"] == "slack_new_message"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from vellum.workflows.inputs.base import BaseInputs
|
|
4
|
+
from vellum.workflows.nodes.displayable.final_output_node import FinalOutputNode
|
|
5
|
+
from vellum.workflows.state.base import BaseState
|
|
6
|
+
from vellum.workflows.workflows.base import BaseWorkflow
|
|
7
|
+
from vellum_ee.workflows.display.utils.exceptions import WorkflowValidationError
|
|
8
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Inputs(BaseInputs):
|
|
12
|
+
input_value: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UnreferencedFinalOutputNode(FinalOutputNode):
|
|
16
|
+
class Outputs(FinalOutputNode.Outputs):
|
|
17
|
+
value = Inputs.input_value
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WorkflowWithUnreferencedFinalOutputNode(BaseWorkflow[Inputs, BaseState]):
|
|
21
|
+
graph = UnreferencedFinalOutputNode
|
|
22
|
+
|
|
23
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
24
|
+
# Intentionally NOT referencing UnreferencedFinalOutputNode.Outputs.value
|
|
25
|
+
result = Inputs.input_value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_serialize_workflow__final_output_node_not_referenced_by_workflow_outputs():
|
|
29
|
+
"""
|
|
30
|
+
Tests that serialization raises a WorkflowValidationError when a workflow has a final output node
|
|
31
|
+
but the workflow outputs don't reference it.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# GIVEN a Workflow with a FinalOutputNode that is not referenced by workflow outputs
|
|
35
|
+
workflow_display = get_workflow_display(workflow_class=WorkflowWithUnreferencedFinalOutputNode)
|
|
36
|
+
|
|
37
|
+
# WHEN we serialize it
|
|
38
|
+
# THEN it should raise a WorkflowValidationError about unreferenced terminal nodes
|
|
39
|
+
with pytest.raises(WorkflowValidationError) as exc_info:
|
|
40
|
+
workflow_display.serialize()
|
|
41
|
+
|
|
42
|
+
# AND the error message should indicate the terminal node is not referenced
|
|
43
|
+
error_message = str(exc_info.value)
|
|
44
|
+
assert "WorkflowWithUnreferencedFinalOutputNode" in error_message
|
|
45
|
+
assert "terminal nodes that are not referenced by workflow outputs" in error_message
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from vellum.workflows import BaseWorkflow
|
|
4
|
+
from vellum.workflows.inputs.base import BaseInputs
|
|
5
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
|
6
|
+
from vellum.workflows.ports.port import Port
|
|
7
|
+
from vellum.workflows.state.base import BaseState
|
|
8
|
+
from vellum_ee.workflows.display.utils.exceptions import WorkflowValidationError
|
|
9
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_workflow_serialization_error__node_points_to_itself():
|
|
13
|
+
"""
|
|
14
|
+
Tests that serialization raises an error when a node creates an infinite loop by pointing to itself.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# GIVEN a simple workflow where a node points to itself
|
|
18
|
+
class StartNode(BaseNode[BaseState]):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
class InfiniteLoopWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
22
|
+
graph = StartNode >> StartNode
|
|
23
|
+
|
|
24
|
+
# WHEN we attempt to serialize the workflow
|
|
25
|
+
workflow_display = get_workflow_display(workflow_class=InfiniteLoopWorkflow)
|
|
26
|
+
|
|
27
|
+
# THEN it should raise a WorkflowValidationError about the self-edge
|
|
28
|
+
with pytest.raises(WorkflowValidationError) as exc_info:
|
|
29
|
+
workflow_display.serialize()
|
|
30
|
+
|
|
31
|
+
# AND the error message should be exact and descriptive
|
|
32
|
+
error_message = str(exc_info.value)
|
|
33
|
+
assert error_message == (
|
|
34
|
+
"Workflow validation error in InfiniteLoopWorkflow: " "Graph contains a self-edge (StartNode >> StartNode)."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_workflow_serialization__node_with_conditional_loop_is_valid():
|
|
39
|
+
"""
|
|
40
|
+
Tests that a node with conditional ports (one looping back, one going forward) is valid.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# GIVEN a workflow where a node has ports with one looping back to itself and one going to another node
|
|
44
|
+
class State(BaseState):
|
|
45
|
+
should_loop: bool = False
|
|
46
|
+
|
|
47
|
+
class StartNode(BaseNode[State]):
|
|
48
|
+
class Ports(BaseNode.Ports):
|
|
49
|
+
loop = Port.on_if(State.should_loop.equals(True))
|
|
50
|
+
end = Port.on_else()
|
|
51
|
+
|
|
52
|
+
class EndNode(BaseNode[State]):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
class ConditionalLoopWorkflow(BaseWorkflow[BaseInputs, State]):
|
|
56
|
+
graph = {
|
|
57
|
+
StartNode.Ports.loop >> StartNode,
|
|
58
|
+
StartNode.Ports.end >> EndNode,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# WHEN we attempt to serialize the workflow
|
|
62
|
+
workflow_display = get_workflow_display(workflow_class=ConditionalLoopWorkflow)
|
|
63
|
+
|
|
64
|
+
# THEN it should NOT raise a WorkflowValidationError
|
|
65
|
+
result = workflow_display.serialize()
|
|
66
|
+
assert result is not None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from vellum.workflows import BaseWorkflow
|
|
2
|
+
from vellum.workflows.inputs import BaseInputs
|
|
3
|
+
from vellum.workflows.nodes import FinalOutputNode
|
|
4
|
+
from vellum.workflows.state import BaseState
|
|
5
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IntInputs(BaseInputs):
|
|
9
|
+
count: int
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OutputNode(FinalOutputNode):
|
|
13
|
+
class Outputs(FinalOutputNode.Outputs):
|
|
14
|
+
value = IntInputs.count
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class IntInputWorkflow(BaseWorkflow[IntInputs, BaseState]):
|
|
18
|
+
graph = OutputNode
|
|
19
|
+
|
|
20
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
21
|
+
value = OutputNode.Outputs.value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_serialize_workflow__int_input_schema_preserved():
|
|
25
|
+
"""
|
|
26
|
+
Tests that an int input has its schema preserved during serialization.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# GIVEN a Workflow that has an int input
|
|
30
|
+
# WHEN we serialize it
|
|
31
|
+
workflow_display = get_workflow_display(workflow_class=IntInputWorkflow)
|
|
32
|
+
serialized_workflow: dict = workflow_display.serialize()
|
|
33
|
+
|
|
34
|
+
# THEN the input variables should include the schema field with type "integer"
|
|
35
|
+
input_variables = serialized_workflow["input_variables"]
|
|
36
|
+
assert len(input_variables) == 1
|
|
37
|
+
|
|
38
|
+
# AND the schema should preserve the int type
|
|
39
|
+
assert input_variables[0]["type"] == "NUMBER"
|
|
40
|
+
assert input_variables[0]["schema"] == {"type": "integer"}
|
vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_serialization.py
CHANGED
|
@@ -187,8 +187,9 @@ def test_trigger_module_paths_are_canonical():
|
|
|
187
187
|
|
|
188
188
|
|
|
189
189
|
def test_integration_trigger_no_entrypoint_node():
|
|
190
|
-
"""IntegrationTrigger workflows
|
|
190
|
+
"""IntegrationTrigger-only workflows should NOT have an ENTRYPOINT node when all branches are trigger-sourced."""
|
|
191
191
|
|
|
192
|
+
# GIVEN an IntegrationTrigger workflow where all branches are sourced from the trigger
|
|
192
193
|
class SlackMessageTrigger(IntegrationTrigger):
|
|
193
194
|
message: str
|
|
194
195
|
|
|
@@ -203,9 +204,10 @@ def test_integration_trigger_no_entrypoint_node():
|
|
|
203
204
|
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
204
205
|
graph = SlackMessageTrigger >> ProcessNode
|
|
205
206
|
|
|
207
|
+
# WHEN we serialize the workflow
|
|
206
208
|
result = get_workflow_display(workflow_class=TestWorkflow).serialize()
|
|
207
209
|
|
|
208
|
-
#
|
|
210
|
+
# THEN the trigger should be serialized
|
|
209
211
|
triggers = result["triggers"]
|
|
210
212
|
assert isinstance(triggers, list)
|
|
211
213
|
assert len(triggers) == 1
|
|
@@ -213,29 +215,21 @@ def test_integration_trigger_no_entrypoint_node():
|
|
|
213
215
|
assert isinstance(trigger, dict)
|
|
214
216
|
trigger_id = trigger["id"]
|
|
215
217
|
|
|
216
|
-
#
|
|
218
|
+
# AND there should be NO ENTRYPOINT node (all branches are trigger-sourced)
|
|
217
219
|
workflow_raw_data = result["workflow_raw_data"]
|
|
218
220
|
assert isinstance(workflow_raw_data, dict)
|
|
219
221
|
nodes = workflow_raw_data["nodes"]
|
|
220
222
|
assert isinstance(nodes, list)
|
|
221
223
|
entrypoint_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "ENTRYPOINT"]
|
|
222
|
-
assert len(entrypoint_nodes) ==
|
|
223
|
-
|
|
224
|
-
entrypoint_node = entrypoint_nodes[0]
|
|
225
|
-
assert isinstance(entrypoint_node, dict)
|
|
226
|
-
entrypoint_node_id = entrypoint_node["id"]
|
|
224
|
+
assert len(entrypoint_nodes) == 0, "IntegrationTrigger-only workflows should NOT have an ENTRYPOINT node"
|
|
227
225
|
|
|
226
|
+
# AND edges should use trigger ID as source_node_id
|
|
228
227
|
edges = workflow_raw_data["edges"]
|
|
229
228
|
assert isinstance(edges, list)
|
|
230
|
-
entrypoint_edges = [e for e in edges if isinstance(e, dict) and e.get("source_node_id") == entrypoint_node_id]
|
|
231
|
-
assert len(entrypoint_edges) == 0
|
|
232
|
-
|
|
233
|
-
# Verify edges use trigger ID as sourceNodeId (not ENTRYPOINT)
|
|
234
229
|
trigger_edges = [e for e in edges if isinstance(e, dict) and e.get("source_node_id") == trigger_id]
|
|
235
230
|
assert len(trigger_edges) > 0, "Should have edges from trigger ID"
|
|
236
231
|
|
|
237
|
-
#
|
|
238
|
-
# ProcessNode should be the only non-terminal, non-entrypoint node
|
|
232
|
+
# AND the edge should connect trigger to the process node
|
|
239
233
|
process_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") not in ("TERMINAL", "ENTRYPOINT")]
|
|
240
234
|
assert len(process_nodes) > 0, "Should have at least one process node"
|
|
241
235
|
process_node = process_nodes[0]
|