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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
2
|
+
|
|
3
|
+
from tests.workflows.basic_tool_calling_node_tool_wrapper.workflow import BasicToolCallingNodeWrapperWorkflow
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_serialize_workflow():
|
|
7
|
+
# GIVEN a Workflow that uses a tool calling node with tool wrapper
|
|
8
|
+
# WHEN we serialize it
|
|
9
|
+
workflow_display = get_workflow_display(workflow_class=BasicToolCallingNodeWrapperWorkflow)
|
|
10
|
+
|
|
11
|
+
serialized_workflow: dict = workflow_display.serialize()
|
|
12
|
+
# THEN we should get a serialized representation of the Workflow
|
|
13
|
+
assert serialized_workflow.keys() == {
|
|
14
|
+
"workflow_raw_data",
|
|
15
|
+
"input_variables",
|
|
16
|
+
"state_variables",
|
|
17
|
+
"output_variables",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# AND its input variables should be what we expect
|
|
21
|
+
input_variables = serialized_workflow["input_variables"]
|
|
22
|
+
assert len(input_variables) == 1
|
|
23
|
+
assert input_variables[0]["key"] == "date_input"
|
|
24
|
+
assert input_variables[0]["type"] == "STRING"
|
|
25
|
+
|
|
26
|
+
# AND its output variables should be what we expect
|
|
27
|
+
output_variables = serialized_workflow["output_variables"]
|
|
28
|
+
assert len(output_variables) == 2
|
|
29
|
+
output_keys = {output["key"] for output in output_variables}
|
|
30
|
+
assert output_keys == {"text", "chat_history"}
|
|
31
|
+
|
|
32
|
+
# AND its raw data should be what we expect
|
|
33
|
+
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
|
34
|
+
|
|
35
|
+
nodes = workflow_raw_data["nodes"]
|
|
36
|
+
assert len(nodes) == 2
|
|
37
|
+
|
|
38
|
+
tool_calling_node = next(node for node in nodes if node.get("type") == "GENERIC")
|
|
39
|
+
|
|
40
|
+
functions_attr = next(attr for attr in tool_calling_node["attributes"] if attr["name"] == "functions")
|
|
41
|
+
functions = functions_attr["value"]["value"]["value"]
|
|
42
|
+
assert len(functions) == 1
|
|
43
|
+
assert functions[0] == {
|
|
44
|
+
"type": "CODE_EXECUTION",
|
|
45
|
+
"name": "get_current_weather",
|
|
46
|
+
"description": "",
|
|
47
|
+
"definition": {
|
|
48
|
+
"state": None,
|
|
49
|
+
"cache_config": None,
|
|
50
|
+
"name": "get_current_weather",
|
|
51
|
+
"description": None,
|
|
52
|
+
"parameters": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"properties": {
|
|
55
|
+
"location": {"type": "string", "description": "The location to get the weather for"},
|
|
56
|
+
"units": {"type": "string", "description": "The unit of temperature", "default": "fahrenheit"},
|
|
57
|
+
},
|
|
58
|
+
"required": ["location"],
|
|
59
|
+
"examples": [{"location": "San Francisco"}, {"location": "New York", "units": "celsius"}],
|
|
60
|
+
},
|
|
61
|
+
"inputs": {
|
|
62
|
+
"date_input": {
|
|
63
|
+
"type": "WORKFLOW_INPUT",
|
|
64
|
+
"input_variable_id": functions[0]["definition"]["inputs"]["date_input"]["input_variable_id"],
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"forced": None,
|
|
68
|
+
"strict": None,
|
|
69
|
+
},
|
|
70
|
+
"src": 'from typing import Annotated\n\n\ndef get_current_weather(\n date_input: str,\n location: Annotated[str, "The location to get the weather for"],\n units: Annotated[str, "The unit of temperature"] = "fahrenheit",\n) -> str:\n return f"The current weather on {date_input} in {location} is sunny with a temperature of 70 degrees {units}."\n', # noqa: E501
|
|
71
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
from vellum.client.types.chat_message_prompt_block import ChatMessagePromptBlock
|
|
2
|
+
from vellum.client.types.rich_text_prompt_block import RichTextPromptBlock
|
|
3
|
+
from vellum.client.types.variable_prompt_block import VariablePromptBlock
|
|
4
|
+
from vellum.workflows.constants import VellumIntegrationProviderType
|
|
5
|
+
from vellum.workflows.nodes.displayable.tool_calling_node import ToolCallingNode
|
|
6
|
+
from vellum.workflows.state.base import BaseState
|
|
7
|
+
from vellum.workflows.types.definition import VellumIntegrationToolDefinition
|
|
8
|
+
from vellum.workflows.workflows.base import BaseInputs, BaseWorkflow
|
|
1
9
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
2
10
|
|
|
3
11
|
from tests.workflows.basic_tool_calling_node_with_vellum_integration_tool.workflow import (
|
|
@@ -66,3 +74,114 @@ def test_serialize_workflow():
|
|
|
66
74
|
assert function["integration_name"] == "GITHUB"
|
|
67
75
|
assert function["name"] == "create_issue"
|
|
68
76
|
assert function["description"] == "Create a new issue in a GitHub repository"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_serialize_workflow__with_toolkit_version():
|
|
80
|
+
"""
|
|
81
|
+
Tests that VellumIntegrationToolDefinition with toolkit_version serializes correctly.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
# GIVEN a VellumIntegrationToolDefinition with toolkit_version
|
|
85
|
+
github_tool_with_version = VellumIntegrationToolDefinition(
|
|
86
|
+
provider=VellumIntegrationProviderType.COMPOSIO,
|
|
87
|
+
integration_name="GITHUB",
|
|
88
|
+
name="create_issue",
|
|
89
|
+
description="Create a new issue in a GitHub repository",
|
|
90
|
+
toolkit_version="1.2.3",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# AND a ToolCallingNode that uses this tool
|
|
94
|
+
class TestInputs(BaseInputs):
|
|
95
|
+
query: str
|
|
96
|
+
|
|
97
|
+
class TestToolCallingNode(ToolCallingNode):
|
|
98
|
+
ml_model = "gpt-4o-mini"
|
|
99
|
+
blocks = [
|
|
100
|
+
ChatMessagePromptBlock(
|
|
101
|
+
chat_role="USER",
|
|
102
|
+
blocks=[
|
|
103
|
+
RichTextPromptBlock(
|
|
104
|
+
blocks=[
|
|
105
|
+
VariablePromptBlock(input_variable="question"),
|
|
106
|
+
],
|
|
107
|
+
),
|
|
108
|
+
],
|
|
109
|
+
),
|
|
110
|
+
]
|
|
111
|
+
functions = [github_tool_with_version]
|
|
112
|
+
prompt_inputs = {"question": TestInputs.query}
|
|
113
|
+
|
|
114
|
+
class TestWorkflow(BaseWorkflow[TestInputs, BaseState]):
|
|
115
|
+
graph = TestToolCallingNode
|
|
116
|
+
|
|
117
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
118
|
+
text = TestToolCallingNode.Outputs.text
|
|
119
|
+
|
|
120
|
+
# WHEN we serialize the workflow
|
|
121
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
122
|
+
serialized_workflow: dict = workflow_display.serialize()
|
|
123
|
+
|
|
124
|
+
# THEN the function should include the toolkit_version
|
|
125
|
+
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
|
126
|
+
tool_calling_node = workflow_raw_data["nodes"][1]
|
|
127
|
+
attributes = tool_calling_node["attributes"]
|
|
128
|
+
functions_attr = next(attr for attr in attributes if attr["name"] == "functions")
|
|
129
|
+
functions_value = functions_attr["value"]["value"]["value"]
|
|
130
|
+
function = functions_value[0]
|
|
131
|
+
|
|
132
|
+
assert function["toolkit_version"] == "1.2.3"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_serialize_workflow__without_toolkit_version():
|
|
136
|
+
"""
|
|
137
|
+
Tests that VellumIntegrationToolDefinition without toolkit_version serializes with null.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# GIVEN a VellumIntegrationToolDefinition without toolkit_version
|
|
141
|
+
github_tool_no_version = VellumIntegrationToolDefinition(
|
|
142
|
+
provider=VellumIntegrationProviderType.COMPOSIO,
|
|
143
|
+
integration_name="SLACK",
|
|
144
|
+
name="send_message",
|
|
145
|
+
description="Send a message to a Slack channel",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# AND a ToolCallingNode that uses this tool
|
|
149
|
+
class TestInputs(BaseInputs):
|
|
150
|
+
query: str
|
|
151
|
+
|
|
152
|
+
class TestToolCallingNode(ToolCallingNode):
|
|
153
|
+
ml_model = "gpt-4o-mini"
|
|
154
|
+
blocks = [
|
|
155
|
+
ChatMessagePromptBlock(
|
|
156
|
+
chat_role="USER",
|
|
157
|
+
blocks=[
|
|
158
|
+
RichTextPromptBlock(
|
|
159
|
+
blocks=[
|
|
160
|
+
VariablePromptBlock(input_variable="question"),
|
|
161
|
+
],
|
|
162
|
+
),
|
|
163
|
+
],
|
|
164
|
+
),
|
|
165
|
+
]
|
|
166
|
+
functions = [github_tool_no_version]
|
|
167
|
+
prompt_inputs = {"question": TestInputs.query}
|
|
168
|
+
|
|
169
|
+
class TestWorkflow(BaseWorkflow[TestInputs, BaseState]):
|
|
170
|
+
graph = TestToolCallingNode
|
|
171
|
+
|
|
172
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
173
|
+
text = TestToolCallingNode.Outputs.text
|
|
174
|
+
|
|
175
|
+
# WHEN we serialize the workflow
|
|
176
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
177
|
+
serialized_workflow: dict = workflow_display.serialize()
|
|
178
|
+
|
|
179
|
+
# THEN the function should have toolkit_version as None
|
|
180
|
+
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
|
181
|
+
tool_calling_node = workflow_raw_data["nodes"][1]
|
|
182
|
+
attributes = tool_calling_node["attributes"]
|
|
183
|
+
functions_attr = next(attr for attr in attributes if attr["name"] == "functions")
|
|
184
|
+
functions_value = functions_attr["value"]["value"]["value"]
|
|
185
|
+
function = functions_value[0]
|
|
186
|
+
|
|
187
|
+
assert function["toolkit_version"] is None
|
|
@@ -33,7 +33,7 @@ def test_serialize_workflow(vellum_client):
|
|
|
33
33
|
input_variables=[],
|
|
34
34
|
output_variables=[],
|
|
35
35
|
),
|
|
36
|
-
deployment=WorkflowDeploymentReleaseWorkflowDeployment(name="test-name"),
|
|
36
|
+
deployment=WorkflowDeploymentReleaseWorkflowDeployment(id="test-deployment-id", name="test-name"),
|
|
37
37
|
description="test-description", # main mock
|
|
38
38
|
release_tags=[
|
|
39
39
|
ReleaseReleaseTag(
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py
CHANGED
|
@@ -47,8 +47,6 @@ def test_serialize_workflow():
|
|
|
47
47
|
|
|
48
48
|
# AND its raw data should be what we expect
|
|
49
49
|
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
|
50
|
-
assert len(workflow_raw_data["edges"]) == 3
|
|
51
|
-
assert len(workflow_raw_data["nodes"]) == 4
|
|
52
50
|
|
|
53
51
|
# AND each node should be serialized correctly
|
|
54
52
|
entrypoint_node = workflow_raw_data["nodes"][0]
|
|
@@ -5,6 +5,7 @@ from vellum.workflows import BaseWorkflow
|
|
|
5
5
|
from vellum.workflows.inputs.base import BaseInputs
|
|
6
6
|
from vellum.workflows.nodes.displayable.set_state_node import SetStateNode
|
|
7
7
|
from vellum.workflows.state.base import BaseState
|
|
8
|
+
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
|
8
9
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
9
10
|
|
|
10
11
|
|
|
@@ -67,7 +68,7 @@ def test_serialize_chat_message_dict_reference_with_definition():
|
|
|
67
68
|
"key": "chat_history",
|
|
68
69
|
"value": {
|
|
69
70
|
"type": "BINARY_EXPRESSION",
|
|
70
|
-
"lhs": {"type": "WORKFLOW_STATE", "state_variable_id": "
|
|
71
|
+
"lhs": {"type": "WORKFLOW_STATE", "state_variable_id": "fff74a8e-752e-4088-9d3e-493e9162bda5"},
|
|
71
72
|
"operator": "+",
|
|
72
73
|
"rhs": {
|
|
73
74
|
"type": "DICTIONARY_REFERENCE",
|
|
@@ -106,3 +107,23 @@ def test_serialize_chat_message_dict_reference_with_definition():
|
|
|
106
107
|
],
|
|
107
108
|
},
|
|
108
109
|
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_serialize_chat_message_trigger_with_message_parameter():
|
|
113
|
+
"""Test that ChatMessageTrigger with message parameter serializes correctly in dataset."""
|
|
114
|
+
# GIVEN a workflow module with a ChatMessageTrigger that has a message parameter
|
|
115
|
+
module_path = "tests.workflows.test_chat_message_trigger_serialization"
|
|
116
|
+
|
|
117
|
+
# WHEN we serialize the module
|
|
118
|
+
result = BaseWorkflowDisplay.serialize_module(module_path)
|
|
119
|
+
|
|
120
|
+
# THEN the dataset should contain the trigger's message in inputs
|
|
121
|
+
assert result.dataset is not None
|
|
122
|
+
assert isinstance(result.dataset, list)
|
|
123
|
+
assert len(result.dataset) == 1
|
|
124
|
+
|
|
125
|
+
# AND the message should be serialized in the inputs as array format
|
|
126
|
+
dataset_row = result.dataset[0]
|
|
127
|
+
assert dataset_row["label"] == "New conversation"
|
|
128
|
+
assert "inputs" in dataset_row
|
|
129
|
+
assert dataset_row["inputs"]["message"] == [{"type": "STRING", "value": "I want to tweet about AI agents"}]
|
vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_trigger_serialization.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""Tests for ChatMessageTrigger serialization."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from vellum import ChatMessage, ChatMessagePromptBlock, JinjaPromptBlock
|
|
6
|
+
from vellum.workflows import BaseWorkflow
|
|
7
|
+
from vellum.workflows.inputs import BaseInputs
|
|
8
|
+
from vellum.workflows.nodes import InlinePromptNode
|
|
9
|
+
from vellum.workflows.nodes.bases import BaseNode
|
|
10
|
+
from vellum.workflows.ports import Port
|
|
11
|
+
from vellum.workflows.references import LazyReference
|
|
12
|
+
from vellum.workflows.state.base import BaseState
|
|
13
|
+
from vellum.workflows.triggers.chat_message import ChatMessageTrigger
|
|
14
|
+
from vellum_ee.workflows.display.utils.exceptions import (
|
|
15
|
+
StateValidationError,
|
|
16
|
+
TriggerValidationError,
|
|
17
|
+
WorkflowValidationError,
|
|
18
|
+
)
|
|
19
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
|
20
|
+
|
|
21
|
+
from tests.workflows.chat_message_trigger_execution.workflows.simple_chat_workflow import SimpleChatWorkflow
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_simple_chat_workflow_serialization():
|
|
25
|
+
"""SimpleChatWorkflow from tests/workflows serializes correctly with ChatMessageTrigger."""
|
|
26
|
+
|
|
27
|
+
# GIVEN a Workflow that uses a ChatMessageTrigger
|
|
28
|
+
# WHEN we serialize it
|
|
29
|
+
workflow_display = get_workflow_display(workflow_class=SimpleChatWorkflow)
|
|
30
|
+
serialized_workflow: dict = workflow_display.serialize()
|
|
31
|
+
|
|
32
|
+
# THEN we should get a serialized representation of the Workflow
|
|
33
|
+
assert serialized_workflow.keys() == {
|
|
34
|
+
"workflow_raw_data",
|
|
35
|
+
"input_variables",
|
|
36
|
+
"state_variables",
|
|
37
|
+
"output_variables",
|
|
38
|
+
"triggers",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# AND the triggers should be serialized correctly
|
|
42
|
+
assert serialized_workflow["triggers"] == [
|
|
43
|
+
{
|
|
44
|
+
"id": "9e14c49b-c6d9-4fe5-9ff2-835fd695fe5f",
|
|
45
|
+
"type": "CHAT_MESSAGE",
|
|
46
|
+
"name": "chat",
|
|
47
|
+
"attributes": [
|
|
48
|
+
{
|
|
49
|
+
"id": "5edbfd78-b634-4305-b2ad-d9feecbd5e5f",
|
|
50
|
+
"key": "message",
|
|
51
|
+
"type": "ARRAY",
|
|
52
|
+
"required": True,
|
|
53
|
+
"default": {
|
|
54
|
+
"type": "ARRAY",
|
|
55
|
+
"value": None,
|
|
56
|
+
},
|
|
57
|
+
"extensions": None,
|
|
58
|
+
"schema": {
|
|
59
|
+
"anyOf": [
|
|
60
|
+
{"type": "string"},
|
|
61
|
+
{
|
|
62
|
+
"type": "array",
|
|
63
|
+
"items": {
|
|
64
|
+
"$ref": "#/$defs/vellum.client.types.array_chat_message_content_item.ArrayChatMessageContentItem" # noqa: E501
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"exec_config": {
|
|
72
|
+
"output": {
|
|
73
|
+
"type": "WORKFLOW_OUTPUT",
|
|
74
|
+
"output_variable_id": "cc1208e9-c043-47f4-abea-c01ac0dbf04c",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
"display_data": {
|
|
78
|
+
"label": "Chat Message",
|
|
79
|
+
"position": {
|
|
80
|
+
"x": 0.0,
|
|
81
|
+
"y": 0.0,
|
|
82
|
+
},
|
|
83
|
+
"z_index": 0,
|
|
84
|
+
"icon": "vellum:icon:message-dots",
|
|
85
|
+
"color": "blue",
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ResponseNode(BaseNode):
|
|
92
|
+
"""Node that returns a simple response."""
|
|
93
|
+
|
|
94
|
+
class Outputs(BaseNode.Outputs):
|
|
95
|
+
response: str = "Hello!"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ChatTriggerWithoutOutput(ChatMessageTrigger):
|
|
99
|
+
"""Chat trigger without Config.output specified."""
|
|
100
|
+
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class WorkflowWithUnspecifiedChatTriggerOutput(BaseWorkflow[BaseInputs, BaseState]):
|
|
105
|
+
"""Workflow using ChatTrigger without output specified."""
|
|
106
|
+
|
|
107
|
+
graph = ChatTriggerWithoutOutput >> ResponseNode
|
|
108
|
+
|
|
109
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
110
|
+
response = ResponseNode.Outputs.response
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_chat_message_trigger_validation__output_not_specified():
|
|
114
|
+
"""
|
|
115
|
+
Tests that serialization adds TriggerValidationError to errors when Chat Trigger output is not specified.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
# GIVEN a Workflow that uses a ChatMessageTrigger without Config.output specified
|
|
119
|
+
workflow_display = get_workflow_display(workflow_class=WorkflowWithUnspecifiedChatTriggerOutput)
|
|
120
|
+
|
|
121
|
+
# WHEN we serialize the workflow
|
|
122
|
+
workflow_display.serialize()
|
|
123
|
+
|
|
124
|
+
# THEN the display_context should contain a TriggerValidationError
|
|
125
|
+
errors = list(workflow_display.display_context.errors)
|
|
126
|
+
assert len(errors) == 1
|
|
127
|
+
|
|
128
|
+
# AND the error should be a TriggerValidationError with the expected message
|
|
129
|
+
error = errors[0]
|
|
130
|
+
assert isinstance(error, TriggerValidationError)
|
|
131
|
+
assert "Chat Trigger output must be specified" in str(error)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_chat_message_trigger_graph_with_duplicate_edges():
|
|
135
|
+
"""ChatMessageTrigger graph with duplicate edges should deduplicate and report error."""
|
|
136
|
+
|
|
137
|
+
# GIVEN a ChatMessageTrigger subclass
|
|
138
|
+
class Chat(ChatMessageTrigger):
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
class Orchestrator(BaseNode):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
# AND a workflow with a graph containing duplicate edges from trigger to node
|
|
145
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
146
|
+
graph = {
|
|
147
|
+
Chat >> Orchestrator,
|
|
148
|
+
Chat >> Orchestrator,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# WHEN we serialize the workflow
|
|
152
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
153
|
+
result: dict = workflow_display.serialize()
|
|
154
|
+
|
|
155
|
+
# THEN the workflow should serialize successfully
|
|
156
|
+
assert "workflow_raw_data" in result
|
|
157
|
+
assert "triggers" in result
|
|
158
|
+
|
|
159
|
+
# AND there should be a CHAT_MESSAGE trigger
|
|
160
|
+
triggers: list = result["triggers"]
|
|
161
|
+
assert len(triggers) == 1
|
|
162
|
+
assert triggers[0]["type"] == "CHAT_MESSAGE"
|
|
163
|
+
|
|
164
|
+
# AND there should NOT be a MANUAL trigger
|
|
165
|
+
manual_triggers = [t for t in triggers if t.get("type") == "MANUAL"]
|
|
166
|
+
assert len(manual_triggers) == 0
|
|
167
|
+
|
|
168
|
+
# AND there should NOT be an ENTRYPOINT node
|
|
169
|
+
workflow_raw_data: dict = result["workflow_raw_data"]
|
|
170
|
+
nodes: list = workflow_raw_data["nodes"]
|
|
171
|
+
entrypoint_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "ENTRYPOINT"]
|
|
172
|
+
assert len(entrypoint_nodes) == 0, "ChatMessageTrigger workflows should NOT have an ENTRYPOINT node"
|
|
173
|
+
|
|
174
|
+
# AND edges should have unique IDs (no duplicates)
|
|
175
|
+
edges: list = workflow_raw_data["edges"]
|
|
176
|
+
edge_ids = [e["id"] for e in edges if isinstance(e, dict)]
|
|
177
|
+
assert len(edge_ids) == len(set(edge_ids)), f"Duplicate edge IDs found: {edge_ids}"
|
|
178
|
+
|
|
179
|
+
# AND there should be an error about the duplicate edge
|
|
180
|
+
errors = list(workflow_display.display_context.errors)
|
|
181
|
+
duplicate_edge_errors = [
|
|
182
|
+
e for e in errors if isinstance(e, WorkflowValidationError) and "duplicate" in str(e).lower()
|
|
183
|
+
]
|
|
184
|
+
assert len(duplicate_edge_errors) == 1, f"Expected 1 duplicate edge error, got {len(duplicate_edge_errors)}"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_graph_with_shared_prefix_different_paths__no_validation_error():
|
|
188
|
+
"""Graph with shared prefix but different paths should not produce validation errors."""
|
|
189
|
+
|
|
190
|
+
# GIVEN a ChatMessageTrigger subclass as the entry point
|
|
191
|
+
class A(ChatMessageTrigger):
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
# AND nodes for a workflow
|
|
195
|
+
class B(BaseNode):
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
class C(BaseNode):
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
class D(BaseNode):
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
# AND a workflow with a graph containing shared prefix but different paths
|
|
205
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
206
|
+
graph = {
|
|
207
|
+
A >> B >> C,
|
|
208
|
+
A >> B >> D,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# WHEN we serialize the workflow
|
|
212
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
213
|
+
result: dict = workflow_display.serialize()
|
|
214
|
+
|
|
215
|
+
# THEN the workflow should serialize successfully
|
|
216
|
+
assert "workflow_raw_data" in result
|
|
217
|
+
|
|
218
|
+
# AND there should be NO validation errors about duplicates
|
|
219
|
+
errors = list(workflow_display.display_context.errors)
|
|
220
|
+
duplicate_errors = [e for e in errors if isinstance(e, WorkflowValidationError) and "duplicate" in str(e).lower()]
|
|
221
|
+
assert len(duplicate_errors) == 0, f"Expected no duplicate errors, got {len(duplicate_errors)}: {duplicate_errors}"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_graph_with_duplicate_paths__validation_error():
|
|
225
|
+
"""Graph with duplicate paths should produce a validation error."""
|
|
226
|
+
|
|
227
|
+
# GIVEN nodes for a workflow
|
|
228
|
+
class A(BaseNode):
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
class B(BaseNode):
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
class C(BaseNode):
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
# AND a workflow with a graph containing duplicate paths
|
|
238
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
239
|
+
graph = {
|
|
240
|
+
A >> B >> C,
|
|
241
|
+
A >> B >> C,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# WHEN we serialize the workflow
|
|
245
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
246
|
+
result: dict = workflow_display.serialize()
|
|
247
|
+
|
|
248
|
+
# THEN the workflow should serialize successfully
|
|
249
|
+
assert "workflow_raw_data" in result
|
|
250
|
+
|
|
251
|
+
# AND there should be a validation error about the duplicate path
|
|
252
|
+
errors = list(workflow_display.display_context.errors)
|
|
253
|
+
duplicate_errors = [e for e in errors if isinstance(e, WorkflowValidationError) and "duplicate" in str(e).lower()]
|
|
254
|
+
assert len(duplicate_errors) == 1, f"Expected 1 duplicate error, got {len(duplicate_errors)}"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_graph_with_different_ports_same_target__no_validation_error():
|
|
258
|
+
"""Graph with different ports from same node to same target should not produce validation errors."""
|
|
259
|
+
|
|
260
|
+
# GIVEN a node with multiple ports
|
|
261
|
+
class A(BaseNode):
|
|
262
|
+
class Ports(BaseNode.Ports):
|
|
263
|
+
foo = Port()
|
|
264
|
+
bar = Port()
|
|
265
|
+
|
|
266
|
+
# AND a target node
|
|
267
|
+
class B(BaseNode):
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
# AND a workflow with a graph where different ports connect to the same target
|
|
271
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
272
|
+
graph = {
|
|
273
|
+
A.Ports.foo >> B,
|
|
274
|
+
A.Ports.bar >> B,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# WHEN we serialize the workflow
|
|
278
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
279
|
+
result: dict = workflow_display.serialize()
|
|
280
|
+
|
|
281
|
+
# THEN the workflow should serialize successfully
|
|
282
|
+
assert "workflow_raw_data" in result
|
|
283
|
+
|
|
284
|
+
# AND there should be NO validation errors about duplicates
|
|
285
|
+
errors = list(workflow_display.display_context.errors)
|
|
286
|
+
duplicate_errors = [e for e in errors if isinstance(e, WorkflowValidationError) and "duplicate" in str(e).lower()]
|
|
287
|
+
assert len(duplicate_errors) == 0, f"Expected no duplicate errors, got {len(duplicate_errors)}: {duplicate_errors}"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_chat_message_trigger_message_wired_to_prompt_inputs():
|
|
291
|
+
"""Tests that ChatMessageTrigger.message can be wired to InlinePromptNode.prompt_inputs."""
|
|
292
|
+
|
|
293
|
+
# GIVEN a ChatMessageTrigger subclass
|
|
294
|
+
class Chat(ChatMessageTrigger):
|
|
295
|
+
class Display(ChatMessageTrigger.Display):
|
|
296
|
+
label: str = "Chat"
|
|
297
|
+
|
|
298
|
+
# AND an InlinePromptNode that uses the trigger's message as a prompt input
|
|
299
|
+
class PromptNode(InlinePromptNode):
|
|
300
|
+
ml_model = "gpt-4o"
|
|
301
|
+
blocks = [
|
|
302
|
+
ChatMessagePromptBlock(
|
|
303
|
+
chat_role="USER",
|
|
304
|
+
blocks=[JinjaPromptBlock(template="{{ message }}")],
|
|
305
|
+
),
|
|
306
|
+
]
|
|
307
|
+
prompt_inputs = {"message": Chat.message}
|
|
308
|
+
|
|
309
|
+
# AND a workflow that wires the trigger to the prompt node
|
|
310
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
|
|
311
|
+
graph = Chat >> PromptNode
|
|
312
|
+
|
|
313
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
314
|
+
result = PromptNode.Outputs.text
|
|
315
|
+
|
|
316
|
+
# WHEN we serialize the workflow
|
|
317
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
|
318
|
+
serialized_workflow: dict = workflow_display.serialize()
|
|
319
|
+
|
|
320
|
+
# THEN the workflow should serialize successfully
|
|
321
|
+
assert "workflow_raw_data" in serialized_workflow
|
|
322
|
+
|
|
323
|
+
# AND the prompt node should have the message input wired correctly
|
|
324
|
+
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
|
325
|
+
nodes = workflow_raw_data["nodes"]
|
|
326
|
+
prompt_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "PROMPT"]
|
|
327
|
+
assert len(prompt_nodes) == 1
|
|
328
|
+
|
|
329
|
+
prompt_node = prompt_nodes[0]
|
|
330
|
+
inputs = prompt_node.get("inputs", [])
|
|
331
|
+
message_input = next((i for i in inputs if i.get("key") == "message"), None)
|
|
332
|
+
assert message_input is not None
|
|
333
|
+
|
|
334
|
+
# AND the input should reference the trigger attribute
|
|
335
|
+
rules = message_input.get("value", {}).get("rules", [])
|
|
336
|
+
trigger_attr_rule = next((r for r in rules if r.get("type") == "TRIGGER_ATTRIBUTE"), None)
|
|
337
|
+
assert trigger_attr_rule is not None
|
|
338
|
+
|
|
339
|
+
# AND the prompt input variable type should be ARRAY
|
|
340
|
+
exec_config = prompt_node.get("data", {}).get("exec_config", {})
|
|
341
|
+
input_variables = exec_config.get("input_variables", [])
|
|
342
|
+
message_var = next((v for v in input_variables if v.get("key") == "message"), None)
|
|
343
|
+
assert message_var is not None
|
|
344
|
+
assert message_var.get("type") == "ARRAY"
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def test_chat_message_trigger_validation__chat_history_none_default():
|
|
348
|
+
"""Tests that serialization adds StateValidationError when chat_history defaults to None instead of empty array."""
|
|
349
|
+
|
|
350
|
+
# GIVEN a state class with chat_history defaulting to None
|
|
351
|
+
class InvalidChatState(BaseState):
|
|
352
|
+
chat_history: Optional[ChatMessage] = None
|
|
353
|
+
|
|
354
|
+
# AND a ChatMessageTrigger with output specified
|
|
355
|
+
class ChatTrigger(ChatMessageTrigger):
|
|
356
|
+
class Config(ChatMessageTrigger.Config):
|
|
357
|
+
output = LazyReference("ChatHistoryNoneWorkflow.Outputs.response")
|
|
358
|
+
|
|
359
|
+
# AND a workflow using the trigger with the invalid state
|
|
360
|
+
class ChatHistoryNoneWorkflow(BaseWorkflow[BaseInputs, InvalidChatState]):
|
|
361
|
+
graph = ChatTrigger >> ResponseNode
|
|
362
|
+
|
|
363
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
364
|
+
response = ResponseNode.Outputs.response
|
|
365
|
+
|
|
366
|
+
# WHEN we serialize the workflow
|
|
367
|
+
workflow_display = get_workflow_display(workflow_class=ChatHistoryNoneWorkflow)
|
|
368
|
+
workflow_display.serialize()
|
|
369
|
+
|
|
370
|
+
# THEN the display_context should contain a StateValidationError
|
|
371
|
+
errors = list(workflow_display.display_context.errors)
|
|
372
|
+
state_errors = [e for e in errors if isinstance(e, StateValidationError)]
|
|
373
|
+
assert len(state_errors) == 1
|
|
374
|
+
|
|
375
|
+
# AND the error should mention chat_history and empty array
|
|
376
|
+
error = state_errors[0]
|
|
377
|
+
assert "chat_history" in str(error)
|
|
378
|
+
assert "empty array" in str(error)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def test_chat_message_trigger_validation__chat_history_missing():
|
|
382
|
+
"""Tests that serialization adds StateValidationError when chat_history is missing from state class."""
|
|
383
|
+
|
|
384
|
+
# GIVEN a state class without chat_history
|
|
385
|
+
class StateMissingChatHistory(BaseState):
|
|
386
|
+
pass
|
|
387
|
+
|
|
388
|
+
# AND a ChatMessageTrigger with output specified
|
|
389
|
+
class ChatTrigger(ChatMessageTrigger):
|
|
390
|
+
class Config(ChatMessageTrigger.Config):
|
|
391
|
+
output = LazyReference("MissingChatHistoryWorkflow.Outputs.response")
|
|
392
|
+
|
|
393
|
+
# AND a workflow using the trigger with the state missing chat_history
|
|
394
|
+
class MissingChatHistoryWorkflow(BaseWorkflow[BaseInputs, StateMissingChatHistory]):
|
|
395
|
+
graph = ChatTrigger >> ResponseNode
|
|
396
|
+
|
|
397
|
+
class Outputs(BaseWorkflow.Outputs):
|
|
398
|
+
response = ResponseNode.Outputs.response
|
|
399
|
+
|
|
400
|
+
# WHEN we serialize the workflow
|
|
401
|
+
workflow_display = get_workflow_display(workflow_class=MissingChatHistoryWorkflow)
|
|
402
|
+
workflow_display.serialize()
|
|
403
|
+
|
|
404
|
+
# THEN the display_context should contain a StateValidationError
|
|
405
|
+
errors = list(workflow_display.display_context.errors)
|
|
406
|
+
state_errors = [e for e in errors if isinstance(e, StateValidationError)]
|
|
407
|
+
assert len(state_errors) == 1
|
|
408
|
+
|
|
409
|
+
# AND the error should mention chat_history is required
|
|
410
|
+
error = state_errors[0]
|
|
411
|
+
assert "chat_history" in str(error)
|
|
412
|
+
assert "require" in str(error)
|