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
|
@@ -183,8 +183,26 @@ def test_tool_calling_node_inline_workflow_context():
|
|
|
183
183
|
tool_prompt_node=tool_prompt_node,
|
|
184
184
|
)
|
|
185
185
|
|
|
186
|
+
# AND we create a state with a function call
|
|
187
|
+
state = ToolCallingState(
|
|
188
|
+
meta=StateMeta(
|
|
189
|
+
node_outputs={
|
|
190
|
+
tool_prompt_node.Outputs.results: [
|
|
191
|
+
FunctionCallVellumValue(
|
|
192
|
+
value=FunctionCall(
|
|
193
|
+
arguments={},
|
|
194
|
+
id="call_test",
|
|
195
|
+
name="MyWorkflow",
|
|
196
|
+
state="FULFILLED",
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
|
|
186
204
|
# AND we create an instance with a context containing generated_files
|
|
187
|
-
function_node = function_node_class()
|
|
205
|
+
function_node = function_node_class(state=state)
|
|
188
206
|
|
|
189
207
|
# Create a parent context with test data
|
|
190
208
|
parent_context = WorkflowContext(
|
|
@@ -192,6 +210,12 @@ def test_tool_calling_node_inline_workflow_context():
|
|
|
192
210
|
)
|
|
193
211
|
function_node._context = parent_context
|
|
194
212
|
|
|
213
|
+
# AND the _inputs should be populated with resolved values from state
|
|
214
|
+
assert function_node._inputs == {
|
|
215
|
+
function_node_class.arguments: {},
|
|
216
|
+
function_node_class.function_call_id: "call_test",
|
|
217
|
+
}
|
|
218
|
+
|
|
195
219
|
# WHEN the function node runs
|
|
196
220
|
outputs = list(function_node.run())
|
|
197
221
|
|
|
@@ -558,11 +582,10 @@ def test_mcp_node_outputs_result():
|
|
|
558
582
|
parameters={},
|
|
559
583
|
)
|
|
560
584
|
|
|
561
|
-
# AND a tool prompt node
|
|
562
585
|
tool_prompt_node = create_tool_prompt_node(
|
|
563
586
|
ml_model="test-model",
|
|
564
587
|
blocks=[],
|
|
565
|
-
functions=[
|
|
588
|
+
functions=[mcp_tool],
|
|
566
589
|
prompt_inputs=None,
|
|
567
590
|
parameters=DEFAULT_PROMPT_PARAMETERS,
|
|
568
591
|
)
|
|
@@ -677,9 +700,93 @@ def test_vellum_integration_node_outputs_result(vellum_client):
|
|
|
677
700
|
assert result_output.value == {"id": 123, "url": "https://github.com/test-owner/test-repo/issues/123"}
|
|
678
701
|
|
|
679
702
|
|
|
680
|
-
def
|
|
703
|
+
def test_vellum_integration_node_error_outputs_result(vellum_client):
|
|
704
|
+
"""Test that VellumIntegrationNode yields error payload as result output when NodeException occurs."""
|
|
705
|
+
|
|
706
|
+
# GIVEN a VellumIntegrationToolDefinition
|
|
707
|
+
github_tool = VellumIntegrationToolDefinition(
|
|
708
|
+
provider=VellumIntegrationProviderType.COMPOSIO,
|
|
709
|
+
integration_name="GITHUB",
|
|
710
|
+
name="create_issue",
|
|
711
|
+
description="Create a new issue in a GitHub repository",
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
# AND a tool prompt node
|
|
715
|
+
tool_prompt_node = create_tool_prompt_node(
|
|
716
|
+
ml_model="test-model",
|
|
717
|
+
blocks=[],
|
|
718
|
+
functions=[github_tool],
|
|
719
|
+
prompt_inputs=None,
|
|
720
|
+
parameters=DEFAULT_PROMPT_PARAMETERS,
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
function_node_class = create_function_node(
|
|
724
|
+
function=github_tool,
|
|
725
|
+
tool_prompt_node=tool_prompt_node,
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
# AND a state with a function call
|
|
729
|
+
state = ToolCallingState(
|
|
730
|
+
meta=StateMeta(
|
|
731
|
+
node_outputs={
|
|
732
|
+
tool_prompt_node.Outputs.results: [
|
|
733
|
+
FunctionCallVellumValue(
|
|
734
|
+
value=FunctionCall(
|
|
735
|
+
arguments={"owner": "test-owner", "repo": "test-repo", "title": "Test Issue"},
|
|
736
|
+
id="call_vellum_error_test",
|
|
737
|
+
name="create_issue",
|
|
738
|
+
state="FULFILLED",
|
|
739
|
+
),
|
|
740
|
+
)
|
|
741
|
+
],
|
|
742
|
+
},
|
|
743
|
+
)
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
# AND mock the vellum client's integration service to raise a 500 error
|
|
747
|
+
vellum_client.integrations.execute_integration_tool.side_effect = ApiError(
|
|
748
|
+
status_code=500,
|
|
749
|
+
body={"detail": "Internal server error occurred while executing the tool."},
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
# AND create a context with the vellum client
|
|
753
|
+
context = WorkflowContext()
|
|
754
|
+
context.vellum_client = vellum_client
|
|
755
|
+
|
|
756
|
+
function_node = function_node_class(state=state)
|
|
757
|
+
function_node._context = context
|
|
758
|
+
|
|
759
|
+
# WHEN the VellumIntegration node runs
|
|
760
|
+
outputs = list(function_node.run())
|
|
761
|
+
|
|
762
|
+
# THEN there should be exactly one output with name "result"
|
|
763
|
+
assert len(outputs) == 1
|
|
764
|
+
result_output = outputs[0]
|
|
765
|
+
assert isinstance(result_output, BaseOutput)
|
|
766
|
+
assert result_output.name == "result"
|
|
767
|
+
assert result_output.is_fulfilled is True
|
|
768
|
+
|
|
769
|
+
# AND the result should contain the error payload
|
|
770
|
+
error_result = result_output.value
|
|
771
|
+
assert isinstance(error_result, dict)
|
|
772
|
+
assert "code" in error_result
|
|
773
|
+
assert "message" in error_result
|
|
774
|
+
assert error_result["code"] == "PROVIDER_ERROR"
|
|
775
|
+
assert "Internal server error occurred while executing the tool" in error_result["message"]
|
|
776
|
+
|
|
777
|
+
# AND the error should also be in chat history
|
|
778
|
+
assert len(state.chat_history) == 1
|
|
779
|
+
function_message = state.chat_history[0]
|
|
780
|
+
assert function_message.role == "FUNCTION"
|
|
781
|
+
assert isinstance(function_message.content, StringChatMessageContent)
|
|
782
|
+
error_data = json.loads(function_message.content.value)
|
|
783
|
+
assert "error" in error_data
|
|
784
|
+
assert error_data["error"]["code"] == "PROVIDER_ERROR"
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def test_tool_calling_node_400_error_preserves_invalid_inputs(vellum_adhoc_prompt_client):
|
|
681
788
|
"""
|
|
682
|
-
Test that ToolCallingNode
|
|
789
|
+
Test that ToolCallingNode preserves INVALID_INPUTS error code when the underlying prompt node returns a 400 error.
|
|
683
790
|
"""
|
|
684
791
|
|
|
685
792
|
# GIVEN a ToolCallingNode with minimal configuration
|
|
@@ -705,10 +812,10 @@ def test_tool_calling_node_400_error_returns_internal_error(vellum_adhoc_prompt_
|
|
|
705
812
|
with pytest.raises(NodeException) as exc_info:
|
|
706
813
|
list(node.run())
|
|
707
814
|
|
|
708
|
-
#
|
|
815
|
+
# THEN the error code should be INVALID_INPUTS (preserved from the underlying prompt node)
|
|
709
816
|
e = exc_info.value
|
|
710
|
-
assert e.code == WorkflowErrorCode.
|
|
711
|
-
assert e.message == "
|
|
817
|
+
assert e.code == WorkflowErrorCode.INVALID_INPUTS
|
|
818
|
+
assert e.message == "Invalid request parameters"
|
|
712
819
|
|
|
713
820
|
|
|
714
821
|
def test_vellum_integration_node_500_error_feeds_back_to_model(vellum_adhoc_prompt_client, vellum_client):
|
|
@@ -800,3 +907,92 @@ def test_vellum_integration_node_500_error_feeds_back_to_model(vellum_adhoc_prom
|
|
|
800
907
|
|
|
801
908
|
# AND the third message should be the assistant's response to the error
|
|
802
909
|
assert chat_history[2].role == "ASSISTANT"
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def test_tool_calling_node_json_output(vellum_adhoc_prompt_client):
|
|
913
|
+
"""
|
|
914
|
+
Tests that ToolCallingNode exposes a json output when the LLM returns valid JSON.
|
|
915
|
+
"""
|
|
916
|
+
|
|
917
|
+
# GIVEN a ToolCallingNode with a simple function
|
|
918
|
+
class TestToolCallingNode(ToolCallingNode):
|
|
919
|
+
ml_model = "gpt-4o-mini"
|
|
920
|
+
blocks = []
|
|
921
|
+
functions = [first_function]
|
|
922
|
+
max_prompt_iterations = 1
|
|
923
|
+
|
|
924
|
+
# AND the LLM returns a JSON response
|
|
925
|
+
expected_json = {"items": ["apple", "banana", "cherry"], "count": 3}
|
|
926
|
+
|
|
927
|
+
def generate_prompt_events(*args: Any, **kwargs: Any) -> Iterator[ExecutePromptEvent]:
|
|
928
|
+
execution_id = str(uuid4())
|
|
929
|
+
events: List[ExecutePromptEvent] = [
|
|
930
|
+
InitiatedExecutePromptEvent(execution_id=execution_id),
|
|
931
|
+
FulfilledExecutePromptEvent(
|
|
932
|
+
execution_id=execution_id,
|
|
933
|
+
outputs=[StringVellumValue(value=json.dumps(expected_json))],
|
|
934
|
+
),
|
|
935
|
+
]
|
|
936
|
+
yield from events
|
|
937
|
+
|
|
938
|
+
vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
|
|
939
|
+
|
|
940
|
+
# WHEN the ToolCallingNode runs
|
|
941
|
+
state = BaseState()
|
|
942
|
+
node = TestToolCallingNode(state=state)
|
|
943
|
+
node_outputs = {}
|
|
944
|
+
for output in node.run():
|
|
945
|
+
if output.is_fulfilled:
|
|
946
|
+
node_outputs[output.name] = output.value
|
|
947
|
+
|
|
948
|
+
# THEN the json output should contain the parsed JSON
|
|
949
|
+
assert "json" in node_outputs
|
|
950
|
+
assert node_outputs["json"] == expected_json
|
|
951
|
+
|
|
952
|
+
# AND the text output should contain the raw JSON string
|
|
953
|
+
assert "text" in node_outputs
|
|
954
|
+
assert node_outputs["text"] == json.dumps(expected_json)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def test_tool_calling_node_json_output_not_present_for_non_json(vellum_adhoc_prompt_client):
|
|
958
|
+
"""
|
|
959
|
+
Tests that ToolCallingNode does not expose a json output when the LLM returns non-JSON text.
|
|
960
|
+
"""
|
|
961
|
+
|
|
962
|
+
# GIVEN a ToolCallingNode with a simple function
|
|
963
|
+
class TestToolCallingNode(ToolCallingNode):
|
|
964
|
+
ml_model = "gpt-4o-mini"
|
|
965
|
+
blocks = []
|
|
966
|
+
functions = [first_function]
|
|
967
|
+
max_prompt_iterations = 1
|
|
968
|
+
|
|
969
|
+
# AND the LLM returns a plain text response (not JSON)
|
|
970
|
+
plain_text_response = "Hello! I can help you with that."
|
|
971
|
+
|
|
972
|
+
def generate_prompt_events(*args: Any, **kwargs: Any) -> Iterator[ExecutePromptEvent]:
|
|
973
|
+
execution_id = str(uuid4())
|
|
974
|
+
events: List[ExecutePromptEvent] = [
|
|
975
|
+
InitiatedExecutePromptEvent(execution_id=execution_id),
|
|
976
|
+
FulfilledExecutePromptEvent(
|
|
977
|
+
execution_id=execution_id,
|
|
978
|
+
outputs=[StringVellumValue(value=plain_text_response)],
|
|
979
|
+
),
|
|
980
|
+
]
|
|
981
|
+
yield from events
|
|
982
|
+
|
|
983
|
+
vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
|
|
984
|
+
|
|
985
|
+
# WHEN the ToolCallingNode runs
|
|
986
|
+
state = BaseState()
|
|
987
|
+
node = TestToolCallingNode(state=state)
|
|
988
|
+
node_outputs = {}
|
|
989
|
+
for output in node.run():
|
|
990
|
+
if output.is_fulfilled:
|
|
991
|
+
node_outputs[output.name] = output.value
|
|
992
|
+
|
|
993
|
+
# THEN the json output should not be present
|
|
994
|
+
assert "json" not in node_outputs
|
|
995
|
+
|
|
996
|
+
# AND the text output should contain the plain text
|
|
997
|
+
assert "text" in node_outputs
|
|
998
|
+
assert node_outputs["text"] == plain_text_response
|
|
@@ -35,14 +35,12 @@ from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior
|
|
|
35
35
|
from vellum.workflows.types.definition import (
|
|
36
36
|
ComposioToolDefinition,
|
|
37
37
|
DeploymentDefinition,
|
|
38
|
-
MCPServer,
|
|
39
38
|
MCPToolDefinition,
|
|
40
|
-
Tool,
|
|
41
39
|
ToolBase,
|
|
42
40
|
VellumIntegrationToolDefinition,
|
|
43
41
|
)
|
|
44
42
|
from vellum.workflows.types.generics import is_workflow_class
|
|
45
|
-
from vellum.workflows.utils.functions import
|
|
43
|
+
from vellum.workflows.utils.functions import get_mcp_tool_name
|
|
46
44
|
|
|
47
45
|
CHAT_HISTORY_VARIABLE = "chat_history"
|
|
48
46
|
|
|
@@ -53,7 +51,8 @@ logger = logging.getLogger(__name__)
|
|
|
53
51
|
class FunctionCallNodeMixin:
|
|
54
52
|
"""Mixin providing common functionality for nodes that handle function calls."""
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
arguments: dict
|
|
55
|
+
function_call_id: Optional[str]
|
|
57
56
|
|
|
58
57
|
def _handle_tool_exception(self, e: Exception, tool_type: str, tool_name: str) -> None:
|
|
59
58
|
"""
|
|
@@ -77,27 +76,9 @@ class FunctionCallNodeMixin:
|
|
|
77
76
|
code=WorkflowErrorCode.NODE_EXECUTION,
|
|
78
77
|
) from e
|
|
79
78
|
|
|
80
|
-
def _extract_function_arguments(self) -> dict:
|
|
81
|
-
"""Extract arguments from function call output."""
|
|
82
|
-
current_index = getattr(self, "state").current_prompt_output_index
|
|
83
|
-
if self.function_call_output and len(self.function_call_output) > current_index:
|
|
84
|
-
function_call = self.function_call_output[current_index]
|
|
85
|
-
if function_call.type == "FUNCTION_CALL" and function_call.value is not None:
|
|
86
|
-
return function_call.value.arguments or {}
|
|
87
|
-
return {}
|
|
88
|
-
|
|
89
|
-
def _extract_function_call_id(self) -> Optional[str]:
|
|
90
|
-
"""Extract function call ID from function call output."""
|
|
91
|
-
current_index = getattr(self, "state").current_prompt_output_index
|
|
92
|
-
if self.function_call_output and len(self.function_call_output) > current_index:
|
|
93
|
-
function_call = self.function_call_output[current_index]
|
|
94
|
-
if function_call.type == "FUNCTION_CALL" and function_call.value is not None:
|
|
95
|
-
return function_call.value.id
|
|
96
|
-
return None
|
|
97
|
-
|
|
98
79
|
def _add_function_result_to_chat_history(self, result: Any, state: ToolCallingState) -> None:
|
|
99
80
|
"""Add function execution result to chat history."""
|
|
100
|
-
function_call_id = self.
|
|
81
|
+
function_call_id = self.function_call_id
|
|
101
82
|
state.chat_history.append(
|
|
102
83
|
ChatMessage(
|
|
103
84
|
role="FUNCTION",
|
|
@@ -119,7 +100,14 @@ class ToolPromptNode(InlinePromptNode[ToolCallingState]):
|
|
|
119
100
|
def run(self) -> Iterator[BaseOutput]:
|
|
120
101
|
if self.max_prompt_iterations is not None and self.state.prompt_iterations >= self.max_prompt_iterations:
|
|
121
102
|
max_iterations_message = f"Maximum number of prompt iterations `{self.max_prompt_iterations}` reached."
|
|
122
|
-
raise NodeException(
|
|
103
|
+
raise NodeException(
|
|
104
|
+
message=max_iterations_message,
|
|
105
|
+
code=WorkflowErrorCode.NODE_EXECUTION,
|
|
106
|
+
raw_data={
|
|
107
|
+
"max_iterations": self.max_prompt_iterations,
|
|
108
|
+
"iterations_reached": self.state.prompt_iterations,
|
|
109
|
+
},
|
|
110
|
+
)
|
|
123
111
|
|
|
124
112
|
generator = super().run()
|
|
125
113
|
with self.state.__quiet__():
|
|
@@ -166,18 +154,9 @@ class DynamicSubworkflowDeploymentNode(SubworkflowDeploymentNode[ToolCallingStat
|
|
|
166
154
|
"""Node that executes a deployment definition with function call output."""
|
|
167
155
|
|
|
168
156
|
def run(self) -> Iterator[BaseOutput]:
|
|
169
|
-
arguments = self._extract_function_arguments()
|
|
170
|
-
|
|
171
157
|
# Mypy doesn't like instance assignments of class attributes. It's safe in our case tho bc it's what
|
|
172
|
-
# we do in the `__init__` method.
|
|
173
|
-
|
|
174
|
-
# ```python
|
|
175
|
-
# subworkflow_inputs = tool_prompt_node.Outputs.results[0]['value']['arguments'].if_(
|
|
176
|
-
# tool_prompt_node.Outputs.results[0]['type'].equals('FUNCTION_CALL'),
|
|
177
|
-
# {},
|
|
178
|
-
# )
|
|
179
|
-
# ```
|
|
180
|
-
self.subworkflow_inputs = arguments # type:ignore[misc]
|
|
158
|
+
# we do in the `__init__` method.
|
|
159
|
+
self.subworkflow_inputs = self.arguments # type:ignore[misc]
|
|
181
160
|
|
|
182
161
|
# Call the parent run method to execute the subworkflow
|
|
183
162
|
outputs = {}
|
|
@@ -196,9 +175,18 @@ class DynamicInlineSubworkflowNode(
|
|
|
196
175
|
"""Node that executes an inline subworkflow with function call output."""
|
|
197
176
|
|
|
198
177
|
def run(self) -> Iterator[BaseOutput]:
|
|
199
|
-
arguments
|
|
178
|
+
# Merge arguments with resolved inputs from __vellum_inputs__
|
|
179
|
+
merged_inputs = self.arguments.copy()
|
|
180
|
+
vellum_inputs = getattr(self.subworkflow, "__vellum_inputs__", {})
|
|
181
|
+
if vellum_inputs:
|
|
182
|
+
for param_name, param_ref in vellum_inputs.items():
|
|
183
|
+
if isinstance(param_ref, BaseDescriptor):
|
|
184
|
+
resolved_value = param_ref.resolve(self.state)
|
|
185
|
+
else:
|
|
186
|
+
resolved_value = param_ref
|
|
187
|
+
merged_inputs[param_name] = resolved_value
|
|
200
188
|
|
|
201
|
-
self.subworkflow_inputs =
|
|
189
|
+
self.subworkflow_inputs = merged_inputs # type: ignore[misc]
|
|
202
190
|
|
|
203
191
|
# Call the parent run method to execute the subworkflow with proper streaming
|
|
204
192
|
outputs = {}
|
|
@@ -221,10 +209,8 @@ class FunctionNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
|
|
|
221
209
|
result: Any
|
|
222
210
|
|
|
223
211
|
def run(self) -> Iterator[BaseOutput]:
|
|
224
|
-
arguments = self._extract_function_arguments()
|
|
225
|
-
|
|
226
212
|
try:
|
|
227
|
-
result = self.function_definition(**arguments)
|
|
213
|
+
result = self.function_definition(**self.arguments)
|
|
228
214
|
except Exception as e:
|
|
229
215
|
self._handle_tool_exception(e, "function", self.function_definition.__name__)
|
|
230
216
|
|
|
@@ -240,18 +226,15 @@ class ComposioNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
|
|
|
240
226
|
composio_tool: ComposioToolDefinition
|
|
241
227
|
|
|
242
228
|
def run(self) -> Iterator[BaseOutput]:
|
|
243
|
-
# Extract arguments from function call
|
|
244
|
-
arguments = self._extract_function_arguments()
|
|
245
|
-
|
|
246
229
|
try:
|
|
247
230
|
# Execute using ComposioService
|
|
248
231
|
composio_service = ComposioService()
|
|
249
232
|
if self.composio_tool.user_id is not None:
|
|
250
233
|
result = composio_service.execute_tool(
|
|
251
|
-
tool_name=self.composio_tool.action, arguments=arguments, user_id=self.composio_tool.user_id
|
|
234
|
+
tool_name=self.composio_tool.action, arguments=self.arguments, user_id=self.composio_tool.user_id
|
|
252
235
|
)
|
|
253
236
|
else:
|
|
254
|
-
result = composio_service.execute_tool(tool_name=self.composio_tool.action, arguments=arguments)
|
|
237
|
+
result = composio_service.execute_tool(tool_name=self.composio_tool.action, arguments=self.arguments)
|
|
255
238
|
except Exception as e:
|
|
256
239
|
self._handle_tool_exception(e, "Composio tool", self.composio_tool.action)
|
|
257
240
|
|
|
@@ -270,11 +253,9 @@ class MCPNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
|
|
|
270
253
|
result: Any
|
|
271
254
|
|
|
272
255
|
def run(self) -> Iterator[BaseOutput]:
|
|
273
|
-
arguments = self._extract_function_arguments()
|
|
274
|
-
|
|
275
256
|
try:
|
|
276
257
|
mcp_service = MCPService()
|
|
277
|
-
result = mcp_service.execute_tool(tool_def=self.mcp_tool, arguments=arguments)
|
|
258
|
+
result = mcp_service.execute_tool(tool_def=self.mcp_tool, arguments=self.arguments)
|
|
278
259
|
except Exception as e:
|
|
279
260
|
self._handle_tool_exception(e, "MCP tool", self.mcp_tool.name)
|
|
280
261
|
|
|
@@ -293,7 +274,6 @@ class VellumIntegrationNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
|
|
|
293
274
|
result: Any
|
|
294
275
|
|
|
295
276
|
def run(self) -> Iterator[BaseOutput]:
|
|
296
|
-
arguments = self._extract_function_arguments()
|
|
297
277
|
vellum_client = self._context.vellum_client
|
|
298
278
|
|
|
299
279
|
try:
|
|
@@ -302,7 +282,8 @@ class VellumIntegrationNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
|
|
|
302
282
|
integration=self.vellum_integration_tool.integration_name,
|
|
303
283
|
provider=self.vellum_integration_tool.provider.value,
|
|
304
284
|
tool_name=self.vellum_integration_tool.name,
|
|
305
|
-
arguments=arguments,
|
|
285
|
+
arguments=self.arguments,
|
|
286
|
+
toolkit_version=self.vellum_integration_tool.toolkit_version,
|
|
306
287
|
)
|
|
307
288
|
except NodeException as e:
|
|
308
289
|
error_payload = {
|
|
@@ -315,7 +296,7 @@ class VellumIntegrationNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
|
|
|
315
296
|
error_payload["error"]["raw_data"] = e.raw_data
|
|
316
297
|
|
|
317
298
|
self._add_function_result_to_chat_history(error_payload, self.state)
|
|
318
|
-
yield
|
|
299
|
+
yield BaseOutput(name="result", value=error_payload["error"])
|
|
319
300
|
return
|
|
320
301
|
except Exception as e:
|
|
321
302
|
self._handle_tool_exception(e, "Vellum Integration tool", self.vellum_integration_tool.name)
|
|
@@ -348,7 +329,7 @@ class ElseNode(BaseNode[ToolCallingState]):
|
|
|
348
329
|
def create_tool_prompt_node(
|
|
349
330
|
ml_model: str,
|
|
350
331
|
blocks: List[Union[PromptBlock, Dict[str, Any]]],
|
|
351
|
-
functions: List[
|
|
332
|
+
functions: List[Union[ToolBase, MCPToolDefinition]],
|
|
352
333
|
prompt_inputs: Optional[EntityInputsInterface],
|
|
353
334
|
parameters: PromptParameters,
|
|
354
335
|
max_prompt_iterations: Optional[int] = None,
|
|
@@ -357,7 +338,7 @@ def create_tool_prompt_node(
|
|
|
357
338
|
settings: Optional[Union[PromptSettings, Dict[str, Any]]] = None,
|
|
358
339
|
) -> Type[ToolPromptNode]:
|
|
359
340
|
if functions and len(functions) > 0:
|
|
360
|
-
prompt_functions: List[
|
|
341
|
+
prompt_functions: List[Union[ToolBase, MCPToolDefinition]] = functions
|
|
361
342
|
else:
|
|
362
343
|
prompt_functions = []
|
|
363
344
|
|
|
@@ -422,8 +403,28 @@ def create_tool_prompt_node(
|
|
|
422
403
|
return node
|
|
423
404
|
|
|
424
405
|
|
|
406
|
+
def _create_function_call_expressions(
|
|
407
|
+
tool_prompt_node: Type[ToolPromptNode],
|
|
408
|
+
) -> tuple[BaseDescriptor[dict], BaseDescriptor[Optional[str]]]:
|
|
409
|
+
"""
|
|
410
|
+
Create expressions to extract arguments and function_call_id from tool_prompt_node outputs.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
A tuple of (arguments_expression, function_call_id_expression)
|
|
414
|
+
|
|
415
|
+
Note: These expressions assume the output at current_prompt_output_index is a valid FUNCTION_CALL.
|
|
416
|
+
The router node ensures this before routing to function nodes.
|
|
417
|
+
"""
|
|
418
|
+
current_output = tool_prompt_node.Outputs.results[ToolCallingState.current_prompt_output_index]
|
|
419
|
+
# Extract arguments and function_call_id directly from the function call value
|
|
420
|
+
# Using coalesce to provide safe fallbacks if the structure is unexpected
|
|
421
|
+
arguments_expr: BaseDescriptor[dict] = current_output["value"]["arguments"].coalesce({})
|
|
422
|
+
function_call_id_expr: BaseDescriptor[Optional[str]] = current_output["value"]["id"].coalesce(None)
|
|
423
|
+
return arguments_expr, function_call_id_expr
|
|
424
|
+
|
|
425
|
+
|
|
425
426
|
def create_router_node(
|
|
426
|
-
functions: List[
|
|
427
|
+
functions: List[Union[ToolBase, MCPToolDefinition]],
|
|
427
428
|
tool_prompt_node: Type[InlinePromptNode[ToolCallingState]],
|
|
428
429
|
) -> Type[RouterNode]:
|
|
429
430
|
"""Create a RouterNode with dynamic ports that route based on tool_prompt_node outputs."""
|
|
@@ -433,14 +434,7 @@ def create_router_node(
|
|
|
433
434
|
Ports = type("Ports", (), {})
|
|
434
435
|
|
|
435
436
|
# Collect all tool names
|
|
436
|
-
tool_names: List[str] = []
|
|
437
|
-
for function in functions:
|
|
438
|
-
if isinstance(function, MCPServer):
|
|
439
|
-
tool_functions: List[MCPToolDefinition] = compile_mcp_tool_definition(function)
|
|
440
|
-
for tool_function in tool_functions:
|
|
441
|
-
tool_names.append(get_mcp_tool_name(tool_function))
|
|
442
|
-
else:
|
|
443
|
-
tool_names.append(get_function_name(function))
|
|
437
|
+
tool_names: List[str] = [get_function_name(function) for function in functions]
|
|
444
438
|
|
|
445
439
|
# Build conditions for each tool name
|
|
446
440
|
conditions = [
|
|
@@ -484,7 +478,7 @@ def create_router_node(
|
|
|
484
478
|
|
|
485
479
|
|
|
486
480
|
def create_function_node(
|
|
487
|
-
function: ToolBase,
|
|
481
|
+
function: Union[ToolBase, MCPToolDefinition],
|
|
488
482
|
tool_prompt_node: Type[ToolPromptNode],
|
|
489
483
|
) -> Type[BaseNode]:
|
|
490
484
|
"""
|
|
@@ -492,11 +486,17 @@ def create_function_node(
|
|
|
492
486
|
|
|
493
487
|
For workflow functions: BaseNode
|
|
494
488
|
For regular functions: BaseNode with direct function call
|
|
489
|
+
For MCP tools: MCPNode
|
|
495
490
|
|
|
496
491
|
Args:
|
|
497
492
|
function: The function to create a node for
|
|
498
493
|
tool_prompt_node: The tool prompt node class
|
|
499
494
|
"""
|
|
495
|
+
if isinstance(function, MCPToolDefinition):
|
|
496
|
+
return create_mcp_tool_node(function, tool_prompt_node)
|
|
497
|
+
|
|
498
|
+
arguments_expr, function_call_id_expr = _create_function_call_expressions(tool_prompt_node)
|
|
499
|
+
|
|
500
500
|
if isinstance(function, DeploymentDefinition):
|
|
501
501
|
deployment = function.deployment_id or function.deployment_name
|
|
502
502
|
release_tag = function.release_tag
|
|
@@ -507,7 +507,8 @@ def create_function_node(
|
|
|
507
507
|
{
|
|
508
508
|
"deployment": deployment,
|
|
509
509
|
"release_tag": release_tag,
|
|
510
|
-
"
|
|
510
|
+
"arguments": arguments_expr,
|
|
511
|
+
"function_call_id": function_call_id_expr,
|
|
511
512
|
"__module__": __name__,
|
|
512
513
|
},
|
|
513
514
|
)
|
|
@@ -520,18 +521,26 @@ def create_function_node(
|
|
|
520
521
|
(ComposioNode,),
|
|
521
522
|
{
|
|
522
523
|
"composio_tool": function,
|
|
523
|
-
"
|
|
524
|
+
"arguments": arguments_expr,
|
|
525
|
+
"function_call_id": function_call_id_expr,
|
|
524
526
|
"__module__": __name__,
|
|
525
527
|
},
|
|
526
528
|
)
|
|
527
529
|
return node
|
|
528
530
|
elif isinstance(function, VellumIntegrationToolDefinition):
|
|
531
|
+
display_class = type(
|
|
532
|
+
f"VellumIntegrationNodeDisplay_{function.name}",
|
|
533
|
+
(VellumIntegrationNode.Display,),
|
|
534
|
+
{"icon": "vellum:icon:plug", "color": "navy"},
|
|
535
|
+
)
|
|
529
536
|
node = type(
|
|
530
537
|
f"VellumIntegrationNode_{function.name}",
|
|
531
538
|
(VellumIntegrationNode,),
|
|
532
539
|
{
|
|
533
540
|
"vellum_integration_tool": function,
|
|
534
|
-
"
|
|
541
|
+
"arguments": arguments_expr,
|
|
542
|
+
"function_call_id": function_call_id_expr,
|
|
543
|
+
"Display": display_class,
|
|
535
544
|
"__module__": __name__,
|
|
536
545
|
},
|
|
537
546
|
)
|
|
@@ -543,7 +552,8 @@ def create_function_node(
|
|
|
543
552
|
(DynamicInlineSubworkflowNode,),
|
|
544
553
|
{
|
|
545
554
|
"subworkflow": function,
|
|
546
|
-
"
|
|
555
|
+
"arguments": arguments_expr,
|
|
556
|
+
"function_call_id": function_call_id_expr,
|
|
547
557
|
"__module__": __name__,
|
|
548
558
|
},
|
|
549
559
|
)
|
|
@@ -567,12 +577,19 @@ def create_function_node(
|
|
|
567
577
|
wrapper.__name__ = func.__name__
|
|
568
578
|
return wrapper
|
|
569
579
|
|
|
580
|
+
display_class = type(
|
|
581
|
+
f"FunctionNodeDisplay_{function.__name__}",
|
|
582
|
+
(FunctionNode.Display,),
|
|
583
|
+
{"icon": "vellum:icon:rectangle-code", "color": "purple"},
|
|
584
|
+
)
|
|
570
585
|
node = type(
|
|
571
586
|
f"FunctionNode_{function.__name__}",
|
|
572
587
|
(FunctionNode,),
|
|
573
588
|
{
|
|
574
589
|
"function_definition": create_function_wrapper(function),
|
|
575
|
-
"
|
|
590
|
+
"arguments": arguments_expr,
|
|
591
|
+
"function_call_id": function_call_id_expr,
|
|
592
|
+
"Display": display_class,
|
|
576
593
|
"__module__": __name__,
|
|
577
594
|
},
|
|
578
595
|
)
|
|
@@ -584,12 +601,14 @@ def create_mcp_tool_node(
|
|
|
584
601
|
tool_def: MCPToolDefinition,
|
|
585
602
|
tool_prompt_node: Type[ToolPromptNode],
|
|
586
603
|
) -> Type[BaseNode]:
|
|
604
|
+
arguments_expr, function_call_id_expr = _create_function_call_expressions(tool_prompt_node)
|
|
587
605
|
node = type(
|
|
588
606
|
f"MCPNode_{tool_def.name}",
|
|
589
607
|
(MCPNode,),
|
|
590
608
|
{
|
|
591
609
|
"mcp_tool": tool_def,
|
|
592
|
-
"
|
|
610
|
+
"arguments": arguments_expr,
|
|
611
|
+
"function_call_id": function_call_id_expr,
|
|
593
612
|
"__module__": __name__,
|
|
594
613
|
},
|
|
595
614
|
)
|
|
@@ -620,8 +639,10 @@ def create_else_node(
|
|
|
620
639
|
return node
|
|
621
640
|
|
|
622
641
|
|
|
623
|
-
def get_function_name(function: ToolBase) -> str:
|
|
624
|
-
if isinstance(function,
|
|
642
|
+
def get_function_name(function: Union[ToolBase, MCPToolDefinition]) -> str:
|
|
643
|
+
if isinstance(function, MCPToolDefinition):
|
|
644
|
+
return get_mcp_tool_name(function)
|
|
645
|
+
elif isinstance(function, DeploymentDefinition):
|
|
625
646
|
name = str(function.deployment_id or function.deployment_name)
|
|
626
647
|
return name.replace("-", "")
|
|
627
648
|
elif isinstance(function, ComposioToolDefinition):
|