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
|
@@ -69,23 +69,24 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
|
|
|
69
69
|
|
|
70
70
|
def run(self) -> Iterator[BaseOutput]:
|
|
71
71
|
mapped_items: Dict[str, List] = defaultdict(list)
|
|
72
|
+
items = self.items or []
|
|
72
73
|
for output_descripter in self.subworkflow.Outputs:
|
|
73
|
-
mapped_items[output_descripter.name] = [None] * len(
|
|
74
|
+
mapped_items[output_descripter.name] = [None] * len(items)
|
|
74
75
|
|
|
75
|
-
if not
|
|
76
|
+
if not items:
|
|
76
77
|
for output_name, output_list in mapped_items.items():
|
|
77
78
|
yield BaseOutput(name=output_name, value=output_list)
|
|
78
79
|
return
|
|
79
80
|
|
|
80
81
|
self._event_queue: Queue[Tuple[int, WorkflowEvent]] = Queue()
|
|
81
|
-
fulfilled_iterations: List[bool] = [False] * len(
|
|
82
|
+
fulfilled_iterations: List[bool] = [False] * len(items)
|
|
82
83
|
|
|
83
|
-
max_workers = self.max_concurrency if self.max_concurrency is not None else len(
|
|
84
|
+
max_workers = self.max_concurrency if self.max_concurrency is not None else len(items)
|
|
84
85
|
|
|
85
86
|
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
86
87
|
futures = []
|
|
87
88
|
current_execution_context = get_execution_context()
|
|
88
|
-
for index, item in enumerate(
|
|
89
|
+
for index, item in enumerate(items):
|
|
89
90
|
future = executor.submit(
|
|
90
91
|
self._context_run_subworkflow,
|
|
91
92
|
item=item,
|
|
@@ -180,6 +181,7 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
|
|
|
180
181
|
inputs=SubworkflowInputsClass(index=index, item=item, items=self.items),
|
|
181
182
|
node_output_mocks=self._context._get_all_node_output_mocks(),
|
|
182
183
|
event_filter=all_workflow_event_filter,
|
|
184
|
+
event_max_size=self._context.event_max_size,
|
|
183
185
|
)
|
|
184
186
|
|
|
185
187
|
for event in events:
|
|
@@ -71,6 +71,8 @@ def test_map_node__use_parallelism():
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def test_map_node__empty_list():
|
|
74
|
+
"""Tests that a map node with an empty list returns an empty output."""
|
|
75
|
+
|
|
74
76
|
# GIVEN a map node that is configured to use the parent's inputs and state
|
|
75
77
|
@MapNode.wrap(items=[])
|
|
76
78
|
class TestNode(BaseNode):
|
|
@@ -92,6 +94,37 @@ def test_map_node__empty_list():
|
|
|
92
94
|
assert fulfilled_output == BaseOutput(name="value", value=[])
|
|
93
95
|
|
|
94
96
|
|
|
97
|
+
def test_map_node__none_items():
|
|
98
|
+
"""Tests that a map node with None items treats it as an empty array and fulfills successfully."""
|
|
99
|
+
|
|
100
|
+
# GIVEN a map node with items set to None
|
|
101
|
+
class TestNode(BaseNode):
|
|
102
|
+
item = MapNode.SubworkflowInputs.item
|
|
103
|
+
|
|
104
|
+
class Outputs(BaseOutputs):
|
|
105
|
+
value: str
|
|
106
|
+
|
|
107
|
+
def run(self) -> Outputs:
|
|
108
|
+
return self.Outputs(value=str(self.item))
|
|
109
|
+
|
|
110
|
+
class TestSubworkflow(BaseWorkflow[MapNode.SubworkflowInputs, BaseState]):
|
|
111
|
+
graph = TestNode
|
|
112
|
+
|
|
113
|
+
class Outputs(BaseOutputs):
|
|
114
|
+
value = TestNode.Outputs.value
|
|
115
|
+
|
|
116
|
+
class TestMapNode(MapNode):
|
|
117
|
+
subworkflow = TestSubworkflow
|
|
118
|
+
|
|
119
|
+
# WHEN the node is run
|
|
120
|
+
node = TestMapNode()
|
|
121
|
+
outputs = list(node.run())
|
|
122
|
+
|
|
123
|
+
# THEN the node should return an empty output without error
|
|
124
|
+
fulfilled_output = outputs[-1]
|
|
125
|
+
assert fulfilled_output == BaseOutput(name="value", value=[])
|
|
126
|
+
|
|
127
|
+
|
|
95
128
|
def test_map_node__inner_try():
|
|
96
129
|
# GIVEN a try wrapped node
|
|
97
130
|
@TryNode.wrap()
|
|
@@ -52,6 +52,7 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
|
52
52
|
inputs=inputs_class(attempt_number=attempt_number),
|
|
53
53
|
event_filter=all_workflow_event_filter,
|
|
54
54
|
node_output_mocks=self._context._get_all_node_output_mocks(),
|
|
55
|
+
event_max_size=self._context.event_max_size,
|
|
55
56
|
)
|
|
56
57
|
|
|
57
58
|
node_outputs: Optional[BaseNode.Outputs] = None
|
|
@@ -36,6 +36,7 @@ class TryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
|
36
36
|
subworkflow_stream = subworkflow.stream(
|
|
37
37
|
event_filter=all_workflow_event_filter,
|
|
38
38
|
node_output_mocks=self._context._get_all_node_output_mocks(),
|
|
39
|
+
event_max_size=self._context.event_max_size,
|
|
39
40
|
)
|
|
40
41
|
|
|
41
42
|
outputs: Optional[BaseOutputs] = None
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
from typing import Optional, Union
|
|
1
|
+
from typing import Generic, Optional, Union
|
|
2
2
|
|
|
3
3
|
from vellum.workflows.constants import AuthorizationType
|
|
4
4
|
from vellum.workflows.nodes.displayable.bases.api_node import BaseAPINode
|
|
5
5
|
from vellum.workflows.types.core import MergeBehavior, VellumSecret
|
|
6
|
+
from vellum.workflows.types.generics import StateType
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
class APINode(BaseAPINode):
|
|
9
|
+
class APINode(BaseAPINode[StateType], Generic[StateType]):
|
|
9
10
|
"""
|
|
10
11
|
Used to execute an API call. This node exists to be backwards compatible with Vellum's API Node, and for most cases,
|
|
11
12
|
you should extend from `BaseAPINode` directly.
|
|
@@ -269,3 +269,41 @@ def test_api_node_passes_timeout_to_vellum_client(vellum_client):
|
|
|
269
269
|
# AND the call should include RequestOptions with the correct timeout
|
|
270
270
|
assert request_options is not None
|
|
271
271
|
assert request_options["timeout_in_seconds"] == 25
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def test_api_node_supports_state_type_parameter(vellum_client):
|
|
275
|
+
"""Test that APINode supports StateType generic parameter."""
|
|
276
|
+
|
|
277
|
+
# GIVEN a state class
|
|
278
|
+
from vellum.workflows.state.base import BaseState
|
|
279
|
+
|
|
280
|
+
class TestState(BaseState):
|
|
281
|
+
counter: int = 0
|
|
282
|
+
|
|
283
|
+
# AND an API node that uses the StateType parameter
|
|
284
|
+
class TestAPINode(APINode[TestState]):
|
|
285
|
+
method = APIRequestMethod.GET
|
|
286
|
+
authorization_type = AuthorizationType.BEARER_TOKEN
|
|
287
|
+
url = "https://example.com"
|
|
288
|
+
bearer_token_value = VellumSecret(name="secret")
|
|
289
|
+
|
|
290
|
+
# AND a mock response from the API
|
|
291
|
+
vellum_client.execute_api.return_value = ExecuteApiResponse(
|
|
292
|
+
status_code=200,
|
|
293
|
+
text='{"result": "success"}',
|
|
294
|
+
json_={"result": "success"},
|
|
295
|
+
headers={"content-type": "application/json"},
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# WHEN we create and run the node with a state instance
|
|
299
|
+
state = TestState()
|
|
300
|
+
node = TestAPINode(state=state)
|
|
301
|
+
outputs = node.run()
|
|
302
|
+
|
|
303
|
+
# THEN the node should run successfully
|
|
304
|
+
assert vellum_client.execute_api.call_count == 1
|
|
305
|
+
assert outputs.status_code == 200
|
|
306
|
+
assert outputs.json == {"result": "success"}
|
|
307
|
+
|
|
308
|
+
# AND the state should be accessible (though not modified by the API node)
|
|
309
|
+
assert state.counter == 0
|
|
@@ -85,6 +85,8 @@ class BasePromptNode(BaseNode[StateType], Generic[StateType]):
|
|
|
85
85
|
continue
|
|
86
86
|
elif event.state == "STREAMING":
|
|
87
87
|
yield BaseOutput(name="results", delta=event.output.value)
|
|
88
|
+
if event.output.type == "STRING":
|
|
89
|
+
yield BaseOutput(name="text", delta=event.output.value)
|
|
88
90
|
elif event.state == "FULFILLED":
|
|
89
91
|
outputs = event.outputs
|
|
90
92
|
yield BaseOutput(name="results", value=event.outputs)
|
|
@@ -123,12 +125,24 @@ class BasePromptNode(BaseNode[StateType], Generic[StateType]):
|
|
|
123
125
|
event: NodeExecutionStreamingEvent,
|
|
124
126
|
workflow_output_descriptor: OutputReference,
|
|
125
127
|
) -> bool:
|
|
128
|
+
# Check if workflow output directly references this node's text output
|
|
129
|
+
text_output = getattr(event.node_definition.Outputs, "text", None)
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
text_output is not None
|
|
133
|
+
and event.output.name == "text"
|
|
134
|
+
and isinstance(workflow_output_descriptor.instance, BaseDescriptor)
|
|
135
|
+
and _contains_reference_to_output(workflow_output_descriptor.instance, text_output)
|
|
136
|
+
):
|
|
137
|
+
return True
|
|
138
|
+
|
|
126
139
|
if event.output.name != "results":
|
|
127
140
|
return False
|
|
128
141
|
|
|
129
142
|
if not isinstance(event.output.delta, str) and not event.output.is_initiated:
|
|
130
143
|
return False
|
|
131
144
|
|
|
145
|
+
# Check if workflow output references this node's text output through a FinalOutputNode
|
|
132
146
|
target_nodes = [e.to_node for port in self.Ports for e in port.edges if e.to_node.__simulates_workflow_output__]
|
|
133
147
|
target_node_output = next(
|
|
134
148
|
(
|
|
@@ -145,4 +159,7 @@ class BasePromptNode(BaseNode[StateType], Generic[StateType]):
|
|
|
145
159
|
if not isinstance(target_node_output.instance, BaseDescriptor):
|
|
146
160
|
return False
|
|
147
161
|
|
|
148
|
-
|
|
162
|
+
if text_output is None:
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
return _contains_reference_to_output(target_node_output.instance, text_output)
|
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
from itertools import chain
|
|
2
2
|
import json
|
|
3
3
|
from uuid import uuid4
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
TYPE_CHECKING,
|
|
6
|
+
Callable,
|
|
7
|
+
ClassVar,
|
|
8
|
+
Generator,
|
|
9
|
+
Generic,
|
|
10
|
+
Iterator,
|
|
11
|
+
List,
|
|
12
|
+
Optional,
|
|
13
|
+
Set,
|
|
14
|
+
Tuple,
|
|
15
|
+
Type,
|
|
16
|
+
Union,
|
|
17
|
+
)
|
|
5
18
|
|
|
6
19
|
import httpx
|
|
20
|
+
import jsonschema
|
|
7
21
|
|
|
8
22
|
from vellum import (
|
|
9
23
|
AdHocExecutePromptEvent,
|
|
@@ -53,6 +67,7 @@ from vellum.workflows.types.definition import (
|
|
|
53
67
|
ComposioToolDefinition,
|
|
54
68
|
DeploymentDefinition,
|
|
55
69
|
MCPServer,
|
|
70
|
+
MCPToolDefinition,
|
|
56
71
|
VellumIntegrationToolDefinition,
|
|
57
72
|
)
|
|
58
73
|
from vellum.workflows.types.generics import StateType, is_workflow_class
|
|
@@ -67,6 +82,60 @@ from vellum.workflows.utils.functions import (
|
|
|
67
82
|
)
|
|
68
83
|
from vellum.workflows.utils.pydantic_schema import normalize_json
|
|
69
84
|
|
|
85
|
+
if TYPE_CHECKING:
|
|
86
|
+
from vellum.workflows.workflows.base import BaseWorkflow
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _get_json_schema_to_validate(parameters_ref: object) -> Optional[dict]:
|
|
90
|
+
"""
|
|
91
|
+
Extracts the JSON schema to validate from a parameters reference.
|
|
92
|
+
|
|
93
|
+
Also normalizes Pydantic models to JSON schema dicts so they can be validated.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
parameters_ref: The parameters reference (NodeReference wrapping PromptParameters)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
The JSON schema dict to validate, or None if no schema found.
|
|
100
|
+
"""
|
|
101
|
+
parameters_instance = getattr(parameters_ref, "instance", None)
|
|
102
|
+
if not parameters_instance:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
custom_params = getattr(parameters_instance, "custom_parameters", None)
|
|
106
|
+
if not isinstance(custom_params, dict):
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
json_schema = custom_params.get("json_schema")
|
|
110
|
+
if json_schema is None:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
# Normalize Pydantic models to JSON schema dicts before validation
|
|
114
|
+
# This handles cases like {"name": "...", "schema": SomePydanticModel}
|
|
115
|
+
json_schema = normalize_json(json_schema)
|
|
116
|
+
|
|
117
|
+
# After normalization, we expect a dict; anything else we ignore
|
|
118
|
+
if not isinstance(json_schema, dict):
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
return json_schema
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _validate_json_schema_structure(schema: dict) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Validates the structure of a JSON schema using the jsonschema library.
|
|
127
|
+
|
|
128
|
+
This uses the JSON Schema meta-schema to validate that the provided schema
|
|
129
|
+
is a valid JSON Schema according to the specification.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
schema: The JSON schema dictionary to validate
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
jsonschema.exceptions.SchemaError: If the schema structure is invalid
|
|
136
|
+
"""
|
|
137
|
+
jsonschema.Draft7Validator.check_schema(schema)
|
|
138
|
+
|
|
70
139
|
|
|
71
140
|
class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
72
141
|
"""
|
|
@@ -87,7 +156,19 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
|
87
156
|
blocks: ClassVar[List[PromptBlock]]
|
|
88
157
|
|
|
89
158
|
# The functions/tools that a Prompt has access to
|
|
90
|
-
functions: Optional[
|
|
159
|
+
functions: Optional[
|
|
160
|
+
List[
|
|
161
|
+
Union[
|
|
162
|
+
FunctionDefinition,
|
|
163
|
+
Callable,
|
|
164
|
+
DeploymentDefinition,
|
|
165
|
+
Type["BaseWorkflow"],
|
|
166
|
+
VellumIntegrationToolDefinition,
|
|
167
|
+
MCPServer,
|
|
168
|
+
MCPToolDefinition,
|
|
169
|
+
]
|
|
170
|
+
]
|
|
171
|
+
] = None
|
|
91
172
|
|
|
92
173
|
parameters: PromptParameters = DEFAULT_PROMPT_PARAMETERS
|
|
93
174
|
expand_meta: Optional[AdHocExpandMeta] = AdHocExpandMeta(finish_reason=True)
|
|
@@ -159,6 +240,14 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
|
159
240
|
normalized_functions.append(
|
|
160
241
|
compile_vellum_integration_tool_definition(function, self._context.vellum_client)
|
|
161
242
|
)
|
|
243
|
+
elif isinstance(function, MCPToolDefinition):
|
|
244
|
+
normalized_functions.append(
|
|
245
|
+
FunctionDefinition(
|
|
246
|
+
name=get_mcp_tool_name(function),
|
|
247
|
+
description=function.description,
|
|
248
|
+
parameters=function.parameters,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
162
251
|
elif isinstance(function, MCPServer):
|
|
163
252
|
tool_definitions = compile_mcp_tool_definition(function)
|
|
164
253
|
for tool_def in tool_definitions:
|
|
@@ -255,6 +344,8 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
|
255
344
|
continue
|
|
256
345
|
elif event.state == "STREAMING":
|
|
257
346
|
yield BaseOutput(name="results", delta=event.output.value)
|
|
347
|
+
if event.output.type == "STRING":
|
|
348
|
+
yield BaseOutput(name="text", delta=event.output.value)
|
|
258
349
|
elif event.state == "FULFILLED":
|
|
259
350
|
if event.meta and event.meta.finish_reason == "LENGTH":
|
|
260
351
|
text_value, json_value = process_additional_prompt_outputs(event.outputs)
|
|
@@ -465,3 +556,19 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
|
465
556
|
Override this method to process the blocks before they are executed.
|
|
466
557
|
"""
|
|
467
558
|
return blocks
|
|
559
|
+
|
|
560
|
+
@classmethod
|
|
561
|
+
def __validate__(cls) -> None:
|
|
562
|
+
"""
|
|
563
|
+
Validates the node configuration, including JSON schema structure in parameters.
|
|
564
|
+
|
|
565
|
+
Raises:
|
|
566
|
+
jsonschema.exceptions.SchemaError: If the JSON schema structure is invalid
|
|
567
|
+
"""
|
|
568
|
+
parameters_ref = getattr(cls, "parameters", None)
|
|
569
|
+
if parameters_ref is None:
|
|
570
|
+
return
|
|
571
|
+
|
|
572
|
+
schema = _get_json_schema_to_validate(parameters_ref)
|
|
573
|
+
if schema is not None:
|
|
574
|
+
_validate_json_schema_structure(schema)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from itertools import chain
|
|
1
2
|
import json
|
|
2
3
|
from uuid import UUID
|
|
3
4
|
from typing import Any, ClassVar, Dict, Generator, Generic, Iterator, List, Optional, Sequence, Set, Union
|
|
@@ -113,12 +114,18 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
|
|
|
113
114
|
if prompt_event_stream is None:
|
|
114
115
|
try:
|
|
115
116
|
prompt_event_stream = self._get_prompt_event_stream()
|
|
116
|
-
next(prompt_event_stream)
|
|
117
|
+
first_event = next(prompt_event_stream)
|
|
117
118
|
except ApiError as e:
|
|
118
119
|
if e.status_code and e.status_code < 500 and self.ml_model_fallbacks is not None:
|
|
119
120
|
prompt_event_stream = self._retry_prompt_stream_with_fallbacks(tried_fallbacks)
|
|
120
121
|
else:
|
|
121
122
|
self._handle_api_error(e)
|
|
123
|
+
else:
|
|
124
|
+
if first_event.state == "REJECTED":
|
|
125
|
+
workflow_error = vellum_error_to_workflow_error(first_event.error)
|
|
126
|
+
raise NodeException.of(workflow_error)
|
|
127
|
+
if first_event.state != "INITIATED":
|
|
128
|
+
prompt_event_stream = chain([first_event], prompt_event_stream)
|
|
122
129
|
|
|
123
130
|
outputs: Optional[List[PromptOutput]] = None
|
|
124
131
|
if prompt_event_stream is not None:
|
|
@@ -127,6 +134,8 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
|
|
|
127
134
|
continue
|
|
128
135
|
elif event.state == "STREAMING":
|
|
129
136
|
yield BaseOutput(name="results", delta=event.output.value)
|
|
137
|
+
if event.output.type == "STRING":
|
|
138
|
+
yield BaseOutput(name="text", delta=event.output.value)
|
|
130
139
|
elif event.state == "FULFILLED":
|
|
131
140
|
outputs = event.outputs
|
|
132
141
|
yield BaseOutput(name="results", value=event.outputs)
|
|
@@ -159,7 +168,9 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
|
|
|
159
168
|
try:
|
|
160
169
|
tried_fallbacks.add(ml_model_fallback)
|
|
161
170
|
prompt_event_stream = self._get_prompt_event_stream(ml_model_fallback=ml_model_fallback)
|
|
162
|
-
next(prompt_event_stream)
|
|
171
|
+
first_event = next(prompt_event_stream)
|
|
172
|
+
if first_event.state != "INITIATED":
|
|
173
|
+
prompt_event_stream = chain([first_event], prompt_event_stream)
|
|
163
174
|
return prompt_event_stream
|
|
164
175
|
except ApiError:
|
|
165
176
|
continue
|
|
@@ -104,7 +104,7 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
|
|
|
104
104
|
output_type = self.__class__.get_output_type()
|
|
105
105
|
code, filepath = self._resolve_code()
|
|
106
106
|
if not self.packages and self.runtime == "PYTHON_3_11_6" and not self._has_secrets_in_code_inputs():
|
|
107
|
-
logs, result = run_code_inline(code, self.code_inputs, output_type, filepath)
|
|
107
|
+
logs, result = run_code_inline(code, self.code_inputs, output_type, filepath, self._context.vellum_client)
|
|
108
108
|
return self.Outputs(result=result, log=logs)
|
|
109
109
|
|
|
110
110
|
else:
|
|
@@ -133,22 +133,16 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
|
|
|
133
133
|
return self.Outputs(result=code_execution_result.output.value, log=code_execution_result.log)
|
|
134
134
|
|
|
135
135
|
def _handle_api_error(self, e: ApiError) -> None:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
message=e.body.get("detail", "Provider credentials is missing or unavailable"),
|
|
139
|
-
code=WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE,
|
|
140
|
-
) from e
|
|
136
|
+
body = e.body if isinstance(e.body, dict) else {}
|
|
137
|
+
message = body.get("detail") or body.get("message") or "Failed to execute code"
|
|
141
138
|
|
|
142
|
-
if e.status_code
|
|
143
|
-
raise NodeException(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
) from e
|
|
139
|
+
if e.status_code == 400:
|
|
140
|
+
raise NodeException(message=message, code=WorkflowErrorCode.INVALID_INPUTS) from e
|
|
141
|
+
|
|
142
|
+
if e.status_code and e.status_code >= 500:
|
|
143
|
+
raise NodeException(message=message, code=WorkflowErrorCode.INTERNAL_ERROR) from e
|
|
147
144
|
|
|
148
|
-
raise NodeException(
|
|
149
|
-
message="Failed to execute code",
|
|
150
|
-
code=WorkflowErrorCode.INTERNAL_ERROR,
|
|
151
|
-
) from e
|
|
145
|
+
raise NodeException(message=message, code=WorkflowErrorCode.NODE_EXECUTION) from e
|
|
152
146
|
|
|
153
147
|
def _has_secrets_in_code_inputs(self) -> bool:
|
|
154
148
|
"""Check if any code_inputs contain VellumSecret instances that require API execution."""
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import pytest
|
|
2
|
+
from datetime import datetime
|
|
2
3
|
import os
|
|
3
4
|
import re
|
|
4
5
|
from typing import Any, List, Union
|
|
5
6
|
|
|
6
7
|
from pydantic import BaseModel
|
|
7
8
|
|
|
8
|
-
from vellum import ArrayInput, CodeExecutorResponse, NumberVellumValue, StringInput, StringVellumValue
|
|
9
|
-
from vellum.client.core.api_error import ApiError
|
|
9
|
+
from vellum import ArrayInput, CodeExecutorResponse, MlModelRead, NumberVellumValue, StringInput, StringVellumValue
|
|
10
10
|
from vellum.client.errors.bad_request_error import BadRequestError
|
|
11
11
|
from vellum.client.errors.forbidden_error import ForbiddenError
|
|
12
12
|
from vellum.client.errors.internal_server_error import InternalServerError
|
|
@@ -24,6 +24,7 @@ from vellum.workflows.inputs.base import BaseInputs
|
|
|
24
24
|
from vellum.workflows.nodes.displayable.code_execution_node import CodeExecutionNode
|
|
25
25
|
from vellum.workflows.references.vellum_secret import VellumSecretReference
|
|
26
26
|
from vellum.workflows.state.base import BaseState, StateMeta
|
|
27
|
+
from vellum.workflows.state.context import WorkflowContext
|
|
27
28
|
from vellum.workflows.types.core import Json
|
|
28
29
|
|
|
29
30
|
|
|
@@ -551,6 +552,51 @@ def main(word: str) -> dict:
|
|
|
551
552
|
}
|
|
552
553
|
|
|
553
554
|
|
|
555
|
+
def test_run_node__run_inline__vellum_client(vellum_client):
|
|
556
|
+
"""Confirm that CodeExecutionNodes can convert a dict to a Pydantic model during inline execution."""
|
|
557
|
+
|
|
558
|
+
# GIVEN a node that subclasses CodeExecutionNode that returns a dict matching Any
|
|
559
|
+
vellum_client.ml_models.retrieve.return_value = MlModelRead(
|
|
560
|
+
id="test-ml-model-id",
|
|
561
|
+
name="Test ML Model",
|
|
562
|
+
description="Test ML Model Description",
|
|
563
|
+
introduced_on=datetime(2025, 1, 2),
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, Any]):
|
|
567
|
+
code = """\
|
|
568
|
+
def main(model_id: str) -> dict:
|
|
569
|
+
ml_model = vellum_client.ml_models.retrieve(model_id)
|
|
570
|
+
return {
|
|
571
|
+
"model_name": ml_model.name,
|
|
572
|
+
"model_id": ml_model.id,
|
|
573
|
+
}
|
|
574
|
+
"""
|
|
575
|
+
runtime = "PYTHON_3_11_6"
|
|
576
|
+
|
|
577
|
+
code_inputs = {
|
|
578
|
+
"model_id": "test-ml-model-id",
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
# WHEN we run the node
|
|
582
|
+
node = ExampleCodeExecutionNode(context=WorkflowContext(vellum_client=vellum_client))
|
|
583
|
+
outputs = node.run()
|
|
584
|
+
|
|
585
|
+
# THEN the node should have produced the outputs we expect
|
|
586
|
+
assert outputs == {
|
|
587
|
+
"result": {
|
|
588
|
+
"model_name": "Test ML Model",
|
|
589
|
+
"model_id": "test-ml-model-id",
|
|
590
|
+
},
|
|
591
|
+
"log": "",
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
# AND
|
|
595
|
+
vellum_client.ml_models.retrieve.assert_called_once_with(
|
|
596
|
+
"test-ml-model-id",
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
|
|
554
600
|
def test_run_node__array_input_with_vellum_values(vellum_client):
|
|
555
601
|
"""Confirm that CodeExecutionNodes can handle arrays containing VellumValue objects."""
|
|
556
602
|
|
|
@@ -796,34 +842,34 @@ Node.js v21.7.3
|
|
|
796
842
|
assert exc_info.value.message == message
|
|
797
843
|
|
|
798
844
|
|
|
799
|
-
def
|
|
800
|
-
"""
|
|
801
|
-
Tests that a 403 error from the API is handled with PROVIDER_CREDENTIALS_UNAVAILABLE error code.
|
|
802
|
-
"""
|
|
845
|
+
def test_run_node__execute_code_api_fails_403__node_execution(vellum_client):
|
|
846
|
+
"""Tests that a 403 error from the code execution API is handled with NODE_EXECUTION error code."""
|
|
803
847
|
|
|
848
|
+
# GIVEN a code execution node
|
|
804
849
|
class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
|
|
805
850
|
code = "def main(): return 'test'"
|
|
806
851
|
runtime = "PYTHON_3_11_6"
|
|
807
852
|
packages = [CodeExecutionPackage(name="requests", version="2.28.0")]
|
|
808
853
|
|
|
854
|
+
# AND the API returns a 403 error
|
|
809
855
|
vellum_client.execute_code.side_effect = ForbiddenError(
|
|
810
856
|
body={
|
|
811
|
-
"detail": "
|
|
857
|
+
"detail": "Access denied to this resource",
|
|
812
858
|
}
|
|
813
859
|
)
|
|
814
860
|
|
|
861
|
+
# WHEN we run the node
|
|
815
862
|
node = ExampleCodeExecutionNode()
|
|
816
863
|
with pytest.raises(NodeException) as exc_info:
|
|
817
864
|
node.run()
|
|
818
865
|
|
|
819
|
-
|
|
820
|
-
assert
|
|
866
|
+
# THEN it should raise NODE_EXECUTION (not PROVIDER_CREDENTIALS_UNAVAILABLE)
|
|
867
|
+
assert exc_info.value.code == WorkflowErrorCode.NODE_EXECUTION
|
|
868
|
+
assert exc_info.value.message == "Access denied to this resource"
|
|
821
869
|
|
|
822
870
|
|
|
823
|
-
def
|
|
824
|
-
"""
|
|
825
|
-
Tests that a 404 error from the API is handled with INVALID_INPUTS error code.
|
|
826
|
-
"""
|
|
871
|
+
def test_run_node__execute_code_api_fails_404__node_execution(vellum_client):
|
|
872
|
+
"""Tests that a 404 error from the API is handled with NODE_EXECUTION error code."""
|
|
827
873
|
|
|
828
874
|
class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
|
|
829
875
|
code = "def main(): return 'test'"
|
|
@@ -840,14 +886,12 @@ def test_run_node__execute_code_api_fails_404__invalid_inputs(vellum_client):
|
|
|
840
886
|
with pytest.raises(NodeException) as exc_info:
|
|
841
887
|
node.run()
|
|
842
888
|
|
|
843
|
-
assert exc_info.value.code == WorkflowErrorCode.
|
|
889
|
+
assert exc_info.value.code == WorkflowErrorCode.NODE_EXECUTION
|
|
844
890
|
assert "Resource not found" in exc_info.value.message
|
|
845
891
|
|
|
846
892
|
|
|
847
893
|
def test_run_node__execute_code_api_fails_500__internal_error(vellum_client):
|
|
848
|
-
"""
|
|
849
|
-
Tests that a 500 error from the API is handled with INTERNAL_ERROR error code.
|
|
850
|
-
"""
|
|
894
|
+
"""Tests that a 500 error from the API is handled with INTERNAL_ERROR error code."""
|
|
851
895
|
|
|
852
896
|
class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
|
|
853
897
|
code = "def main(): return 'test'"
|
|
@@ -865,21 +909,18 @@ def test_run_node__execute_code_api_fails_500__internal_error(vellum_client):
|
|
|
865
909
|
node.run()
|
|
866
910
|
|
|
867
911
|
assert exc_info.value.code == WorkflowErrorCode.INTERNAL_ERROR
|
|
868
|
-
assert exc_info.value.message == "
|
|
912
|
+
assert exc_info.value.message == "Internal server error occurred"
|
|
869
913
|
|
|
870
914
|
|
|
871
|
-
def
|
|
872
|
-
"""
|
|
873
|
-
Tests that a 4xx error without a 'message' field falls back to 'detail' field.
|
|
874
|
-
"""
|
|
915
|
+
def test_run_node__execute_code_api_fails_400__invalid_inputs(vellum_client):
|
|
916
|
+
"""Tests that a 400 error from the API is handled with INVALID_INPUTS error code."""
|
|
875
917
|
|
|
876
918
|
class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
|
|
877
919
|
code = "def main(): return 'test'"
|
|
878
920
|
runtime = "PYTHON_3_11_6"
|
|
879
921
|
packages = [CodeExecutionPackage(name="requests", version="2.28.0")]
|
|
880
922
|
|
|
881
|
-
vellum_client.execute_code.side_effect =
|
|
882
|
-
status_code=422,
|
|
923
|
+
vellum_client.execute_code.side_effect = BadRequestError(
|
|
883
924
|
body={
|
|
884
925
|
"detail": "Invalid request parameters",
|
|
885
926
|
},
|
|
@@ -4,6 +4,7 @@ import sys
|
|
|
4
4
|
import traceback
|
|
5
5
|
from typing import Any, Optional, Tuple, Union
|
|
6
6
|
|
|
7
|
+
from vellum import Vellum
|
|
7
8
|
from vellum.workflows.constants import undefined
|
|
8
9
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
|
9
10
|
from vellum.workflows.exceptions import NodeException
|
|
@@ -41,6 +42,7 @@ def run_code_inline(
|
|
|
41
42
|
inputs: EntityInputsInterface,
|
|
42
43
|
output_type: Any,
|
|
43
44
|
filepath: str,
|
|
45
|
+
vellum_client: Vellum,
|
|
44
46
|
) -> Tuple[str, Any]:
|
|
45
47
|
log_buffer = io.StringIO()
|
|
46
48
|
|
|
@@ -54,6 +56,7 @@ def run_code_inline(
|
|
|
54
56
|
"__arg__inputs": wrapped_inputs,
|
|
55
57
|
"__arg__out": None,
|
|
56
58
|
"print": _inline_print,
|
|
59
|
+
"vellum_client": vellum_client,
|
|
57
60
|
}
|
|
58
61
|
run_args = [f"{name}=__arg__inputs['{name}']" for name, value in inputs.items() if value is not undefined]
|
|
59
62
|
execution_code = f"""\
|