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
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
from
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import warnings
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
|
|
2
4
|
|
|
3
|
-
from pydantic import Field, field_serializer
|
|
5
|
+
from pydantic import ConfigDict, Field, SerializationInfo, field_serializer, model_serializer, model_validator
|
|
4
6
|
|
|
5
7
|
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
|
8
|
+
from vellum.utils.files.mixin import VellumFileMixin
|
|
6
9
|
from vellum.workflows.inputs.base import BaseInputs
|
|
7
10
|
from vellum.workflows.nodes.mocks import MockNodeExecution
|
|
8
11
|
from vellum.workflows.outputs.base import BaseOutputs
|
|
@@ -18,23 +21,107 @@ class DatasetRow(UniversalBaseModel):
|
|
|
18
21
|
id: Optional unique identifier for the dataset row
|
|
19
22
|
label: String label for the dataset row
|
|
20
23
|
inputs: BaseInputs instance or dict containing the input data
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
workflow_trigger: Optional Trigger instance for this scenario
|
|
25
|
+
mocks: Optional sequence of node output mocks for testing scenarios
|
|
23
26
|
"""
|
|
24
27
|
|
|
28
|
+
model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)
|
|
29
|
+
|
|
25
30
|
id: Optional[str] = None
|
|
26
31
|
label: str
|
|
27
32
|
inputs: Union[BaseInputs, Dict[str, Any]] = Field(default_factory=BaseInputs)
|
|
28
|
-
workflow_trigger: Optional[
|
|
29
|
-
|
|
33
|
+
workflow_trigger: Optional[BaseTrigger] = None
|
|
34
|
+
mocks: Optional[Sequence[Union[BaseOutputs, MockNodeExecution]]] = None
|
|
35
|
+
# DEPRECATED: node_output_mocks - use mocks instead. Remove in v2.0.0
|
|
36
|
+
node_output_mocks: Optional[Sequence[Union[BaseOutputs, MockNodeExecution]]] = Field(
|
|
37
|
+
default=None,
|
|
38
|
+
exclude=True, # Don't include in serialized output
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
@model_validator(mode="after")
|
|
42
|
+
def _handle_deprecated_node_output_mocks(self) -> "DatasetRow":
|
|
43
|
+
"""Handle deprecated node_output_mocks field by mapping it to mocks."""
|
|
44
|
+
if self.node_output_mocks is not None:
|
|
45
|
+
warnings.warn(
|
|
46
|
+
"The 'node_output_mocks' parameter is deprecated and will be removed in v2.0.0. "
|
|
47
|
+
"Please use 'mocks' instead.",
|
|
48
|
+
DeprecationWarning,
|
|
49
|
+
stacklevel=2,
|
|
50
|
+
)
|
|
51
|
+
# If mocks is not set, use node_output_mocks value
|
|
52
|
+
if self.mocks is None:
|
|
53
|
+
object.__setattr__(self, "mocks", self.node_output_mocks)
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
@model_serializer(mode="wrap")
|
|
57
|
+
def serialize_full_model(self, handler: Callable[[Any], Any], info: SerializationInfo) -> Dict[str, Any]:
|
|
58
|
+
"""Serialize the model and add node_id field computed from then_outputs."""
|
|
59
|
+
serialized = handler(self)
|
|
60
|
+
if not isinstance(serialized, dict):
|
|
61
|
+
return serialized
|
|
62
|
+
|
|
63
|
+
# Add deprecation error if node_output_mocks was used - remove in v2.0.0
|
|
64
|
+
if self.node_output_mocks is not None:
|
|
65
|
+
add_error = info.context.get("add_error") if info.context else None
|
|
66
|
+
if add_error is not None:
|
|
67
|
+
add_error(
|
|
68
|
+
Exception(
|
|
69
|
+
f'Dataset row "{self.label}": "node_output_mocks" is deprecated. '
|
|
70
|
+
'Please use "mocks" instead. This will be removed in v2.0.0.'
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if "mocks" in serialized and serialized.get("mocks") is None:
|
|
75
|
+
serialized.pop("mocks")
|
|
76
|
+
|
|
77
|
+
if "workflow_trigger" in serialized:
|
|
78
|
+
value = serialized.pop("workflow_trigger")
|
|
79
|
+
if value is not None:
|
|
80
|
+
serialized["workflow_trigger_id"] = value
|
|
81
|
+
|
|
82
|
+
# Merge trigger attribute values into inputs if workflow_trigger is present
|
|
83
|
+
if self.workflow_trigger is not None:
|
|
84
|
+
trigger_attrs = self.workflow_trigger.to_trigger_attribute_values()
|
|
85
|
+
for ref, attr_value in trigger_attrs.items():
|
|
86
|
+
# Convert datetime objects to ISO format strings for JSON serialization
|
|
87
|
+
if isinstance(attr_value, datetime):
|
|
88
|
+
attr_value = attr_value.isoformat()
|
|
89
|
+
serialized["inputs"][ref.name] = attr_value
|
|
90
|
+
|
|
91
|
+
if "id" in serialized and serialized.get("id") is None:
|
|
92
|
+
serialized.pop("id")
|
|
93
|
+
|
|
94
|
+
return serialized
|
|
95
|
+
|
|
96
|
+
@field_serializer("workflow_trigger")
|
|
97
|
+
def serialize_workflow_trigger(self, workflow_trigger: Optional[BaseTrigger]) -> Optional[str]:
|
|
98
|
+
"""
|
|
99
|
+
Custom serializer for workflow_trigger that converts it to a string ID.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
workflow_trigger: Optional workflow trigger instance
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
String representation of the trigger ID, or None if no trigger
|
|
106
|
+
"""
|
|
107
|
+
if workflow_trigger is None:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
return str(workflow_trigger.__class__.__id__)
|
|
30
111
|
|
|
31
112
|
@field_serializer("inputs")
|
|
32
|
-
def serialize_inputs(self, inputs: Union[BaseInputs, Dict[str, Any]]) -> Dict[str, Any]:
|
|
113
|
+
def serialize_inputs(self, inputs: Union[BaseInputs, Dict[str, Any]], info: SerializationInfo) -> Dict[str, Any]:
|
|
33
114
|
"""
|
|
34
115
|
Custom serializer for inputs that converts it to a dictionary.
|
|
35
116
|
|
|
117
|
+
When add_error is provided in the serialization context, this method will
|
|
118
|
+
validate file-type inputs (VellumFileMixin) by attempting to serialize them,
|
|
119
|
+
and collect errors for fields that fail to serialize while still including
|
|
120
|
+
valid fields.
|
|
121
|
+
|
|
36
122
|
Args:
|
|
37
123
|
inputs: Either a BaseInputs instance or dict to serialize
|
|
124
|
+
info: Serialization info containing context
|
|
38
125
|
|
|
39
126
|
Returns:
|
|
40
127
|
Dictionary representation of the inputs
|
|
@@ -43,31 +130,49 @@ class DatasetRow(UniversalBaseModel):
|
|
|
43
130
|
return inputs
|
|
44
131
|
|
|
45
132
|
result = {}
|
|
133
|
+
add_error = info.context.get("add_error") if info.context else None
|
|
134
|
+
|
|
46
135
|
for input_descriptor, value in inputs:
|
|
47
|
-
if
|
|
136
|
+
if input_descriptor.name.startswith("__"):
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# For file types, validate by attempting to serialize and catch errors
|
|
140
|
+
if add_error is not None and isinstance(value, VellumFileMixin):
|
|
141
|
+
try:
|
|
142
|
+
# Trigger validation by serializing the file type
|
|
143
|
+
value.model_dump(mode="json", by_alias=True, exclude_none=True)
|
|
144
|
+
result[input_descriptor.name] = value
|
|
145
|
+
except Exception as field_error:
|
|
146
|
+
error_msg = (
|
|
147
|
+
f'Dataset row "{self.label}": input "{input_descriptor.name}" '
|
|
148
|
+
f"failed serialization: {field_error}"
|
|
149
|
+
)
|
|
150
|
+
add_error(Exception(error_msg))
|
|
151
|
+
else:
|
|
48
152
|
result[input_descriptor.name] = value
|
|
49
153
|
|
|
50
154
|
return result
|
|
51
155
|
|
|
52
|
-
@field_serializer("
|
|
53
|
-
def
|
|
54
|
-
self,
|
|
156
|
+
@field_serializer("mocks")
|
|
157
|
+
def serialize_mocks(
|
|
158
|
+
self, mocks: Optional[Sequence[Union[BaseOutputs, MockNodeExecution]]], info
|
|
55
159
|
) -> Optional[List[Dict[str, Any]]]:
|
|
56
160
|
"""
|
|
57
|
-
Custom serializer for
|
|
161
|
+
Custom serializer for mocks that normalizes both BaseOutputs and MockNodeExecution
|
|
58
162
|
to a consistent dict format with node_id, when_condition, and then_outputs.
|
|
59
163
|
|
|
60
164
|
Args:
|
|
61
|
-
|
|
165
|
+
mocks: Optional sequence of BaseOutputs or MockNodeExecution instances
|
|
166
|
+
info: Serialization info containing context
|
|
62
167
|
|
|
63
168
|
Returns:
|
|
64
169
|
List of normalized mock execution dicts, or None if input is None
|
|
65
170
|
"""
|
|
66
|
-
if
|
|
171
|
+
if mocks is None:
|
|
67
172
|
return None
|
|
68
173
|
|
|
69
174
|
result = []
|
|
70
|
-
for mock in
|
|
175
|
+
for mock in mocks:
|
|
71
176
|
if isinstance(mock, MockNodeExecution):
|
|
72
177
|
mock_exec = mock
|
|
73
178
|
else:
|
|
@@ -76,6 +181,6 @@ class DatasetRow(UniversalBaseModel):
|
|
|
76
181
|
then_outputs=mock,
|
|
77
182
|
)
|
|
78
183
|
|
|
79
|
-
result.append(mock_exec.model_dump())
|
|
184
|
+
result.append(mock_exec.model_dump(context=info.context))
|
|
80
185
|
|
|
81
186
|
return result
|
|
@@ -54,7 +54,7 @@ def test_base_inputs_explicit_none_should_raise_on_fields_without_defaults(field
|
|
|
54
54
|
TestInputs() # type: ignore[call-arg]
|
|
55
55
|
|
|
56
56
|
assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
|
|
57
|
-
assert "Required input variables required_string should have defined value" == str(exc_info.value)
|
|
57
|
+
assert "Required input variables 'required_string' should have defined value" == str(exc_info.value)
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def test_base_inputs_explicit_none_should_raise_on_required_fields_with_none():
|
|
@@ -69,7 +69,7 @@ def test_base_inputs_explicit_none_should_raise_on_required_fields_with_none():
|
|
|
69
69
|
TestInputs(required_string=None) # type: ignore[arg-type]
|
|
70
70
|
|
|
71
71
|
assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
|
|
72
|
-
assert "Required input variables required_string should have defined value" == str(exc_info.value)
|
|
72
|
+
assert "Required input variables 'required_string' should have defined value" == str(exc_info.value)
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
def test_base_inputs_empty_value():
|
|
@@ -84,7 +84,7 @@ def test_base_inputs_empty_value():
|
|
|
84
84
|
|
|
85
85
|
# THEN it should raise a NodeException with the correct error message and code
|
|
86
86
|
assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
|
|
87
|
-
assert "Required input variables required_string should have defined value" == str(exc_info.value)
|
|
87
|
+
assert "Required input variables 'required_string' should have defined value" == str(exc_info.value)
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
def test_base_inputs_with_default():
|
|
@@ -33,6 +33,7 @@ def test_vellum_integration_service_get_tool_definition_success(vellum_client):
|
|
|
33
33
|
"required": ["repo", "title"],
|
|
34
34
|
},
|
|
35
35
|
output_parameters={},
|
|
36
|
+
toolkit_version="1.0.0",
|
|
36
37
|
)
|
|
37
38
|
mock_client.integrations.retrieve_integration_tool_definition.return_value = tool_definition_response
|
|
38
39
|
|
|
@@ -61,6 +62,55 @@ def test_vellum_integration_service_get_tool_definition_success(vellum_client):
|
|
|
61
62
|
integration_name="GITHUB",
|
|
62
63
|
integration_provider="COMPOSIO",
|
|
63
64
|
tool_name="GITHUB_CREATE_AN_ISSUE",
|
|
65
|
+
toolkit_version=None,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_vellum_integration_service_get_tool_definition_with_toolkit_version(vellum_client):
|
|
70
|
+
"""Test that toolkit_version is passed through when retrieving tool definitions"""
|
|
71
|
+
# GIVEN a mock client configured to return a tool definition
|
|
72
|
+
mock_client = vellum_client
|
|
73
|
+
tool_definition_response = ComponentsSchemasComposioToolDefinition(
|
|
74
|
+
integration=Integration(
|
|
75
|
+
id=str(uuid4()),
|
|
76
|
+
provider="COMPOSIO",
|
|
77
|
+
name="GITHUB",
|
|
78
|
+
),
|
|
79
|
+
label="GITHUB_CREATE_AN_ISSUE",
|
|
80
|
+
name="GITHUB_CREATE_AN_ISSUE",
|
|
81
|
+
description="Create a new issue in a GitHub repository",
|
|
82
|
+
input_parameters={
|
|
83
|
+
"type": "object",
|
|
84
|
+
"properties": {
|
|
85
|
+
"repo": {"type": "string", "description": "Repository name"},
|
|
86
|
+
},
|
|
87
|
+
"required": ["repo"],
|
|
88
|
+
},
|
|
89
|
+
output_parameters={},
|
|
90
|
+
toolkit_version="2.0.0",
|
|
91
|
+
)
|
|
92
|
+
mock_client.integrations.retrieve_integration_tool_definition.return_value = tool_definition_response
|
|
93
|
+
|
|
94
|
+
# WHEN we request a tool definition with a specific toolkit_version
|
|
95
|
+
service = VellumIntegrationService(client=mock_client)
|
|
96
|
+
result = service.get_tool_definition(
|
|
97
|
+
integration="GITHUB",
|
|
98
|
+
provider="COMPOSIO",
|
|
99
|
+
tool_name="GITHUB_CREATE_AN_ISSUE",
|
|
100
|
+
toolkit_version="2.0.0",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# THEN the tool definition should be returned with the toolkit_version preserved
|
|
104
|
+
assert isinstance(result, VellumIntegrationToolDetails)
|
|
105
|
+
assert result.name == "GITHUB_CREATE_AN_ISSUE"
|
|
106
|
+
assert result.toolkit_version == "2.0.0"
|
|
107
|
+
|
|
108
|
+
# AND the API should have been called with the toolkit_version parameter
|
|
109
|
+
mock_client.integrations.retrieve_integration_tool_definition.assert_called_once_with(
|
|
110
|
+
integration_name="GITHUB",
|
|
111
|
+
integration_provider="COMPOSIO",
|
|
112
|
+
tool_name="GITHUB_CREATE_AN_ISSUE",
|
|
113
|
+
toolkit_version="2.0.0",
|
|
64
114
|
)
|
|
65
115
|
|
|
66
116
|
|
|
@@ -128,6 +178,40 @@ def test_vellum_integration_service_execute_tool_success(vellum_client):
|
|
|
128
178
|
"title": "Test Issue",
|
|
129
179
|
"body": "Test body",
|
|
130
180
|
},
|
|
181
|
+
toolkit_version=None,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_vellum_integration_service_execute_tool_with_toolkit_version(vellum_client):
|
|
186
|
+
"""Test that toolkit_version is passed through when executing tools"""
|
|
187
|
+
# GIVEN a mock client configured to return a successful response
|
|
188
|
+
mock_client = vellum_client
|
|
189
|
+
mock_client.integrations = mock.MagicMock()
|
|
190
|
+
|
|
191
|
+
mock_response = mock.MagicMock()
|
|
192
|
+
mock_response.data = {"success": True, "result": "executed with version"}
|
|
193
|
+
mock_client.integrations.execute_integration_tool.return_value = mock_response
|
|
194
|
+
|
|
195
|
+
# WHEN we execute a tool with a specific toolkit_version
|
|
196
|
+
service = VellumIntegrationService(client=mock_client)
|
|
197
|
+
result = service.execute_tool(
|
|
198
|
+
integration="GITHUB",
|
|
199
|
+
provider="COMPOSIO",
|
|
200
|
+
tool_name="GITHUB_CREATE_AN_ISSUE",
|
|
201
|
+
arguments={"repo": "user/repo"},
|
|
202
|
+
toolkit_version="2.0.0",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# THEN the execution result should contain expected data
|
|
206
|
+
assert result["success"] is True
|
|
207
|
+
|
|
208
|
+
# AND the API should have been called with the toolkit_version parameter
|
|
209
|
+
mock_client.integrations.execute_integration_tool.assert_called_once_with(
|
|
210
|
+
integration_name="GITHUB",
|
|
211
|
+
integration_provider="COMPOSIO",
|
|
212
|
+
tool_name="GITHUB_CREATE_AN_ISSUE",
|
|
213
|
+
arguments={"repo": "user/repo"},
|
|
214
|
+
toolkit_version="2.0.0",
|
|
131
215
|
)
|
|
132
216
|
|
|
133
217
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Dict
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
2
|
|
|
3
3
|
from vellum.client.core.api_error import ApiError
|
|
4
4
|
from vellum.workflows.constants import VellumIntegrationProviderType
|
|
@@ -25,6 +25,7 @@ class VellumIntegrationService:
|
|
|
25
25
|
integration: str,
|
|
26
26
|
provider: str,
|
|
27
27
|
tool_name: str,
|
|
28
|
+
toolkit_version: Optional[str] = None,
|
|
28
29
|
) -> VellumIntegrationToolDetails:
|
|
29
30
|
"""Retrieve a tool definition from Vellum integrations.
|
|
30
31
|
|
|
@@ -32,6 +33,9 @@ class VellumIntegrationService:
|
|
|
32
33
|
integration: The integration name (e.g., "GITHUB", "SLACK")
|
|
33
34
|
provider: The integration provider name (e.g., "COMPOSIO")
|
|
34
35
|
tool_name: The tool's unique name as specified by the provider
|
|
36
|
+
toolkit_version: The version of the toolkit to use. Pass 'latest' to get the
|
|
37
|
+
latest version, or a specific version string to pin it. If not provided,
|
|
38
|
+
uses the provider's default.
|
|
35
39
|
|
|
36
40
|
Returns:
|
|
37
41
|
VellumIntegrationToolDetails containing the tool definition with parameters
|
|
@@ -44,6 +48,7 @@ class VellumIntegrationService:
|
|
|
44
48
|
integration_name=integration,
|
|
45
49
|
integration_provider=provider,
|
|
46
50
|
tool_name=tool_name,
|
|
51
|
+
toolkit_version=toolkit_version,
|
|
47
52
|
)
|
|
48
53
|
|
|
49
54
|
return VellumIntegrationToolDetails(
|
|
@@ -52,6 +57,7 @@ class VellumIntegrationService:
|
|
|
52
57
|
name=response.name,
|
|
53
58
|
description=response.description,
|
|
54
59
|
parameters=response.input_parameters,
|
|
60
|
+
toolkit_version=response.toolkit_version,
|
|
55
61
|
)
|
|
56
62
|
except Exception as e:
|
|
57
63
|
error_message = f"Failed to retrieve tool definition for {tool_name}: {str(e)}"
|
|
@@ -66,6 +72,7 @@ class VellumIntegrationService:
|
|
|
66
72
|
provider: str,
|
|
67
73
|
tool_name: str,
|
|
68
74
|
arguments: Dict[str, Any],
|
|
75
|
+
toolkit_version: Optional[str] = None,
|
|
69
76
|
) -> Dict[str, Any]:
|
|
70
77
|
"""Execute a tool through Vellum integrations.
|
|
71
78
|
|
|
@@ -74,6 +81,9 @@ class VellumIntegrationService:
|
|
|
74
81
|
provider: The integration provider name (e.g., "COMPOSIO")
|
|
75
82
|
tool_name: The tool's unique name as specified by the provider
|
|
76
83
|
arguments: Arguments to pass to the tool
|
|
84
|
+
toolkit_version: The version of the toolkit to use. Pass 'latest' to get the
|
|
85
|
+
latest version, or a specific version string to pin it. If not provided,
|
|
86
|
+
uses the provider's default.
|
|
77
87
|
|
|
78
88
|
Returns:
|
|
79
89
|
Dict containing the execution result data
|
|
@@ -88,6 +98,7 @@ class VellumIntegrationService:
|
|
|
88
98
|
integration_provider=provider,
|
|
89
99
|
tool_name=tool_name,
|
|
90
100
|
arguments=arguments,
|
|
101
|
+
toolkit_version=toolkit_version,
|
|
91
102
|
)
|
|
92
103
|
|
|
93
104
|
# Return the data from the response
|
vellum/workflows/loaders/base.py
CHANGED
|
@@ -9,6 +9,8 @@ class BaseWorkflowFinder(importlib.abc.MetaPathFinder, ABC):
|
|
|
9
9
|
Abstract base class for workflow finders that support custom error message formatting.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
namespace: str
|
|
13
|
+
|
|
12
14
|
@abstractmethod
|
|
13
15
|
def format_error_message(self, error_message: str) -> str:
|
|
14
16
|
"""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from abc import ABC, ABCMeta
|
|
1
|
+
from abc import ABC, ABCMeta
|
|
2
2
|
from collections.abc import Callable as CollectionsCallable
|
|
3
3
|
from dataclasses import field
|
|
4
4
|
from functools import cached_property, reduce
|
|
@@ -46,7 +46,7 @@ from vellum.workflows.state.context import WorkflowContext
|
|
|
46
46
|
from vellum.workflows.types.core import MergeBehavior
|
|
47
47
|
from vellum.workflows.types.generics import StateType
|
|
48
48
|
from vellum.workflows.types.utils import get_class_attr_names, get_original_base, infer_types
|
|
49
|
-
from vellum.workflows.utils.uuids import uuid4_from_hash
|
|
49
|
+
from vellum.workflows.utils.uuids import generate_entity_id_from_path, uuid4_from_hash
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def _is_nested_class(nested: Any, parent: Type) -> bool:
|
|
@@ -115,7 +115,21 @@ class BaseNodeMeta(ABCMeta):
|
|
|
115
115
|
dct["Ports"] = type(f"{name}.Ports", (base.Ports,), ports_dct)
|
|
116
116
|
break
|
|
117
117
|
|
|
118
|
-
if "Display"
|
|
118
|
+
if "Display" in dct:
|
|
119
|
+
display_class = dct["Display"]
|
|
120
|
+
parent_display_class = next(
|
|
121
|
+
(base.Display for base in bases if hasattr(base, "Display")),
|
|
122
|
+
None,
|
|
123
|
+
)
|
|
124
|
+
# Ensure user-defined Display class inherits from parent's Display
|
|
125
|
+
if parent_display_class and not issubclass(display_class, parent_display_class):
|
|
126
|
+
filtered_bases = tuple(base for base in display_class.__bases__ if base is not object)
|
|
127
|
+
dct["Display"] = type(
|
|
128
|
+
f"{name}.Display",
|
|
129
|
+
(parent_display_class,) + filtered_bases,
|
|
130
|
+
{**display_class.__dict__, "__module__": dct["__module__"]},
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
119
133
|
for base in reversed(bases):
|
|
120
134
|
if issubclass(base, BaseNode):
|
|
121
135
|
dct["Display"] = type(
|
|
@@ -159,7 +173,8 @@ class BaseNodeMeta(ABCMeta):
|
|
|
159
173
|
node_class.ExternalInputs.__parent_class__ = node_class
|
|
160
174
|
|
|
161
175
|
# Use new ID generation (module + qualname)
|
|
162
|
-
|
|
176
|
+
# generate_entity_id_from_path normalizes the module path to filter out UUID namespace for stable ID generation
|
|
177
|
+
node_class.__id__ = generate_entity_id_from_path(f"{node_class.__module__}.{node_class.__qualname__}")
|
|
163
178
|
|
|
164
179
|
node_class.__output_ids__ = {
|
|
165
180
|
ref.name: uuid4_from_hash(f"{node_class.__id__}|{ref.name}")
|
|
@@ -247,17 +262,6 @@ class BaseNodeMeta(ABCMeta):
|
|
|
247
262
|
yield attr_value
|
|
248
263
|
yielded_attr_names.add(attr_name)
|
|
249
264
|
|
|
250
|
-
@abstractmethod
|
|
251
|
-
def __validate__(cls) -> None:
|
|
252
|
-
"""
|
|
253
|
-
Validates the node.
|
|
254
|
-
Subclasses can override this method to implement their specific validation logic.
|
|
255
|
-
Called during serialization or explicit validation.
|
|
256
|
-
|
|
257
|
-
Default implementation performs no validation.
|
|
258
|
-
"""
|
|
259
|
-
pass
|
|
260
|
-
|
|
261
265
|
|
|
262
266
|
class _BaseNodeTriggerMeta(type):
|
|
263
267
|
def __eq__(self, other: Any) -> bool:
|
|
@@ -325,6 +329,9 @@ class BaseNode(Generic[StateType], ABC, BaseExecutable, metaclass=BaseNodeMeta):
|
|
|
325
329
|
|
|
326
330
|
icon: Optional[str] = None
|
|
327
331
|
color: Optional[str] = None
|
|
332
|
+
x: Optional[float] = None
|
|
333
|
+
y: Optional[float] = None
|
|
334
|
+
z_index: Optional[int] = None
|
|
328
335
|
|
|
329
336
|
class Trigger(metaclass=_BaseNodeTriggerMeta):
|
|
330
337
|
node_class: Type["BaseNode"]
|
|
@@ -383,8 +390,11 @@ class BaseNode(Generic[StateType], ABC, BaseExecutable, metaclass=BaseNodeMeta):
|
|
|
383
390
|
all_deps_invoked = all(dep in node_classes_invoked for dep in dependencies)
|
|
384
391
|
return all_deps_invoked
|
|
385
392
|
|
|
393
|
+
if cls.merge_behavior == MergeBehavior.CUSTOM:
|
|
394
|
+
return False
|
|
395
|
+
|
|
386
396
|
raise NodeException(
|
|
387
|
-
message="Invalid Trigger Node Specification",
|
|
397
|
+
message=f"Invalid Trigger Node Specification: {cls.merge_behavior}",
|
|
388
398
|
code=WorkflowErrorCode.INVALID_INPUTS,
|
|
389
399
|
)
|
|
390
400
|
|
|
@@ -571,3 +581,14 @@ class BaseNode(Generic[StateType], ABC, BaseExecutable, metaclass=BaseNodeMeta):
|
|
|
571
581
|
"""
|
|
572
582
|
|
|
573
583
|
return False
|
|
584
|
+
|
|
585
|
+
@classmethod
|
|
586
|
+
def __validate__(cls) -> None:
|
|
587
|
+
"""
|
|
588
|
+
Validates the node.
|
|
589
|
+
Subclasses can override this method to implement their specific validation logic.
|
|
590
|
+
Called during serialization or explicit validation.
|
|
591
|
+
|
|
592
|
+
Default implementation performs no validation.
|
|
593
|
+
"""
|
|
594
|
+
pass
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from uuid import UUID
|
|
3
|
-
from typing import Optional, Set
|
|
3
|
+
from typing import Any, Dict, Optional, Set
|
|
4
4
|
|
|
5
5
|
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
|
6
6
|
from vellum.client.types.string_vellum_value_request import StringVellumValueRequest
|
|
7
|
+
from vellum.workflows import BaseWorkflow
|
|
7
8
|
from vellum.workflows.constants import undefined
|
|
8
9
|
from vellum.workflows.descriptors.tests.test_utils import FixtureState
|
|
10
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
|
9
11
|
from vellum.workflows.inputs.base import BaseInputs
|
|
10
12
|
from vellum.workflows.nodes import FinalOutputNode
|
|
11
13
|
from vellum.workflows.nodes.bases.base import BaseNode
|
|
@@ -15,6 +17,7 @@ from vellum.workflows.references.constant import ConstantValueReference
|
|
|
15
17
|
from vellum.workflows.references.node import NodeReference
|
|
16
18
|
from vellum.workflows.references.output import OutputReference
|
|
17
19
|
from vellum.workflows.state.base import BaseState, StateMeta
|
|
20
|
+
from vellum.workflows.workflows.event_filters import all_workflow_event_filter
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
def test_base_node__node_resolution__unset_pydantic_fields():
|
|
@@ -379,3 +382,103 @@ def test_base_node__ports_inheritance__cumulative_ports():
|
|
|
379
382
|
# Potentially in the future, we support inheriting ports from multiple parents.
|
|
380
383
|
# For now, we take only the declared ports, so that not all nodes have the default port.
|
|
381
384
|
assert ports == ["bar"]
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def test_base_node__trigger_should_initiate__invalid_merge_behavior():
|
|
388
|
+
"""
|
|
389
|
+
Tests that an invalid merge behavior raises a NodeException with the invalid value in the error message.
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
# GIVEN a node with an invalid merge behavior
|
|
393
|
+
class InvalidMergeBehaviorNode(BaseNode):
|
|
394
|
+
class Trigger(BaseNode.Trigger):
|
|
395
|
+
merge_behavior = "INVALID_MERGE_BEHAVIOR" # type: ignore[assignment]
|
|
396
|
+
|
|
397
|
+
# AND a workflow that uses the node
|
|
398
|
+
class InvalidMergeBehaviorWorkflow(BaseWorkflow):
|
|
399
|
+
graph = InvalidMergeBehaviorNode
|
|
400
|
+
|
|
401
|
+
# WHEN we stream the workflow
|
|
402
|
+
workflow = InvalidMergeBehaviorWorkflow()
|
|
403
|
+
events = list(workflow.stream(event_filter=all_workflow_event_filter))
|
|
404
|
+
|
|
405
|
+
# THEN the workflow should reject
|
|
406
|
+
workflow_rejected_event = events[-1]
|
|
407
|
+
assert workflow_rejected_event.name == "workflow.execution.rejected"
|
|
408
|
+
|
|
409
|
+
# AND the error should have the correct code
|
|
410
|
+
assert workflow_rejected_event.error.code == WorkflowErrorCode.INVALID_INPUTS
|
|
411
|
+
|
|
412
|
+
# AND the error message should contain the invalid merge behavior
|
|
413
|
+
assert "INVALID_MERGE_BEHAVIOR" in workflow_rejected_event.error.message
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def test_base_node__int_input_preserves_type_when_float_passed():
|
|
417
|
+
"""
|
|
418
|
+
Tests that an int workflow input is correctly coerced to int when a float value is passed.
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
# GIVEN an Inputs class with an int field
|
|
422
|
+
class Inputs(BaseInputs):
|
|
423
|
+
total_requests: int
|
|
424
|
+
|
|
425
|
+
# AND a custom node that references the int input
|
|
426
|
+
class CustomNode(BaseNode):
|
|
427
|
+
total_requests = Inputs.total_requests
|
|
428
|
+
|
|
429
|
+
class Outputs(BaseOutputs):
|
|
430
|
+
result: int
|
|
431
|
+
|
|
432
|
+
def run(self) -> BaseOutputs:
|
|
433
|
+
# This should work without error - range() requires an int, not a float
|
|
434
|
+
result = len(range(self.total_requests))
|
|
435
|
+
return self.Outputs(result=result)
|
|
436
|
+
|
|
437
|
+
# AND a workflow that uses the custom node
|
|
438
|
+
class IntInputWorkflow(BaseWorkflow[Inputs, BaseState]):
|
|
439
|
+
graph = CustomNode
|
|
440
|
+
|
|
441
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
442
|
+
result = CustomNode.Outputs.result
|
|
443
|
+
|
|
444
|
+
# WHEN we run the workflow with a float value for an int input
|
|
445
|
+
# This simulates what happens when the API returns a NUMBER value as a float
|
|
446
|
+
workflow = IntInputWorkflow()
|
|
447
|
+
raw_inputs: Dict[str, Any] = {"total_requests": 5.0}
|
|
448
|
+
inputs = Inputs(**raw_inputs)
|
|
449
|
+
|
|
450
|
+
# THEN the input should be coerced to an integer at construction time
|
|
451
|
+
assert inputs.total_requests == 5
|
|
452
|
+
assert type(inputs.total_requests) is int
|
|
453
|
+
|
|
454
|
+
# AND the workflow should complete successfully
|
|
455
|
+
final_event = workflow.run(inputs=inputs)
|
|
456
|
+
assert final_event.name == "workflow.execution.fulfilled"
|
|
457
|
+
|
|
458
|
+
# AND the result should be correct
|
|
459
|
+
assert final_event.outputs.result == 5
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def test_base_node__bytes_output_raises_serialization_error():
|
|
463
|
+
"""Test that returning bytes in node outputs rejects the workflow execution."""
|
|
464
|
+
|
|
465
|
+
class BytesOutputNode(BaseNode):
|
|
466
|
+
class Outputs(BaseNode.Outputs):
|
|
467
|
+
result: str
|
|
468
|
+
|
|
469
|
+
def run(self) -> "BytesOutputNode.Outputs":
|
|
470
|
+
b = b"hello"
|
|
471
|
+
return self.Outputs(result=b) # type: ignore[arg-type]
|
|
472
|
+
|
|
473
|
+
class BytesWorkflow(BaseWorkflow):
|
|
474
|
+
graph = BytesOutputNode
|
|
475
|
+
|
|
476
|
+
workflow = BytesWorkflow()
|
|
477
|
+
|
|
478
|
+
# WHEN we run the workflow
|
|
479
|
+
result = workflow.run()
|
|
480
|
+
|
|
481
|
+
# THEN the execution is rejected with a helpful error
|
|
482
|
+
assert result.name == "workflow.execution.rejected"
|
|
483
|
+
assert result.error.code == WorkflowErrorCode.INVALID_OUTPUTS
|
|
484
|
+
assert "bytes" in result.error.message.lower()
|
|
@@ -84,6 +84,7 @@ class InlineSubworkflowNode(
|
|
|
84
84
|
event_filter=all_workflow_event_filter,
|
|
85
85
|
node_output_mocks=self._context._get_all_node_output_mocks(),
|
|
86
86
|
cancel_signal=self._child_cancel_signal,
|
|
87
|
+
event_max_size=self._context.event_max_size,
|
|
87
88
|
)
|
|
88
89
|
|
|
89
90
|
outputs: Optional[BaseOutputs] = None
|
|
@@ -116,7 +116,7 @@ def test_inline_subworkflow_node__base_inputs_validation():
|
|
|
116
116
|
|
|
117
117
|
# AND the error message should indicate the missing required input
|
|
118
118
|
assert e.value.code == WorkflowErrorCode.INVALID_INPUTS
|
|
119
|
-
assert "Required input variables required_input should have defined value" == str(e.value)
|
|
119
|
+
assert "Required input variables 'required_input' should have defined value" == str(e.value)
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
def test_inline_subworkflow_node__with_adornment():
|