From cd4464d6b19037f5038fe5f96ff3ca415115b2f6 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 29 Jul 2025 19:32:48 -0300 Subject: [PATCH] refactor(tests): parameterize template tests and update timeout use (#9224) * refactor: Simplify flow execution validation by removing unnecessary asyncio.wait_for calls Updated the validate_flow_execution function to directly use the client.post and client.get methods with a timeout parameter, improving code readability and maintainability. This change eliminates redundant timeout handling while ensuring consistent timeout values across API calls. * refactor: Enhance template tests for improved structure and validation Refactored the template tests in `test_starter_projects.py` to utilize parameterization for better readability and maintainability. Introduced helper functions to retrieve template files and disabled tracing for all tests. Updated individual test methods to validate JSON structure, flow execution, and endpoint validation, ensuring comprehensive coverage of template functionality. This change streamlines the testing process and enhances the robustness of the test suite. * refactor: Update project metadata and import paths in starter project JSON files Modified the metadata section in multiple starter project JSON files to reflect updated code hashes and module paths, transitioning from 'lfx' to 'langflow' components. This change enhances consistency across the codebase and ensures that the correct modules are referenced for improved maintainability and clarity. * chore: Update template test commands to utilize parallel execution Modified the commands in the Makefile and CI workflows to include the `-n auto` option for pytest, enabling parallel test execution for the starter project template tests. This change enhances test performance and efficiency across the codebase. * chore: Remove news-aggregated.json file Deleted the news-aggregated.json file * Update test_template_validation.py * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/template-tests.yml | 2 +- Makefile | 2 +- .../Sequential Tasks Agents.json | 397 +++++++++++++----- .../starter_projects/Youtube Analysis.json | 352 ++++++++++++---- .../langflow/utils/template_validation.py | 14 +- src/backend/tests/conftest.py | 8 +- .../components/astra/test_astra_component.py | 2 +- src/backend/tests/locust/locustfile.py | 2 +- .../components/agents/test_agent_component.py | 2 +- .../unit/template/test_starter_projects.py | 186 ++++---- .../unit/utils/test_template_validation.py | 4 +- 12 files changed, 666 insertions(+), 307 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c26710661..aeb38da64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -232,7 +232,7 @@ jobs: - name: Test all starter project templates run: | - uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v + uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v -n auto # https://github.com/langchain-ai/langchain/blob/master/.github/workflows/check_diffs.yml ci_success: diff --git a/.github/workflows/template-tests.yml b/.github/workflows/template-tests.yml index 1ad1b9ad1..760739931 100644 --- a/.github/workflows/template-tests.yml +++ b/.github/workflows/template-tests.yml @@ -37,4 +37,4 @@ jobs: - name: Test all starter project templates run: | - uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v \ No newline at end of file + uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v -n auto \ No newline at end of file diff --git a/Makefile b/Makefile index a5c345d1c..c334a1846 100644 --- a/Makefile +++ b/Makefile @@ -169,7 +169,7 @@ tests: ## run unit, integration, coverage tests template_tests: ## run all starter project template tests @echo 'Running Starter Project Template Tests...' - @uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v + @uv run pytest src/backend/tests/unit/template/test_starter_projects.py -v -n auto ###################### # CODE QUALITY diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json index d7c4aa7fd..165673b38 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json @@ -9,12 +9,16 @@ "dataType": "Prompt", "id": "Prompt-WvveL", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_prompt", "id": "Agent-X1iAT", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -33,12 +37,16 @@ "dataType": "Prompt", "id": "Prompt-6JL4E", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_prompt", "id": "Agent-EQcU8", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -57,12 +65,17 @@ "dataType": "Agent", "id": "Agent-EQcU8", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "finance_agent_output", "id": "Prompt-WvveL", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -81,12 +94,16 @@ "dataType": "ChatInput", "id": "ChatInput-NuUHZ", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-b7nmW", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -105,12 +122,16 @@ "dataType": "Prompt", "id": "Prompt-ajhmq", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "system_prompt", "id": "Agent-b7nmW", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -129,12 +150,16 @@ "dataType": "Agent", "id": "Agent-b7nmW", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-EQcU8", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -153,12 +178,17 @@ "dataType": "Agent", "id": "Agent-b7nmW", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "research_agent_output", "id": "Prompt-WvveL", - "inputTypes": ["Message", "Text"], + "inputTypes": [ + "Message", + "Text" + ], "type": "str" } }, @@ -177,12 +207,16 @@ "dataType": "CalculatorComponent", "id": "CalculatorComponent-0P2yI", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-X1iAT", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -201,12 +235,16 @@ "dataType": "YfinanceComponent", "id": "YfinanceComponent-hAneS", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-EQcU8", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -225,12 +263,16 @@ "dataType": "TavilySearchComponent", "id": "TavilySearchComponent-DTUmi", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-b7nmW", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -249,12 +291,18 @@ "dataType": "Agent", "id": "Agent-X1iAT", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-gbqPo", - "inputTypes": ["Data", "DataFrame", "Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "other" } }, @@ -273,7 +321,9 @@ "display_name": "Finance Agent", "id": "Agent-EQcU8", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -322,7 +372,9 @@ "required_inputs": null, "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -354,7 +406,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -475,7 +529,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -683,7 +739,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -751,7 +809,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "list_add_label": "Add More", "name": "tools", @@ -812,7 +872,9 @@ "display_name": "Analysis & Editor Agent", "id": "Agent-X1iAT", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -861,7 +923,9 @@ "required_inputs": null, "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -893,7 +957,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1014,7 +1080,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1222,7 +1290,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1290,7 +1360,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "list_add_label": "Add More", "name": "tools", @@ -1351,7 +1423,9 @@ "display_name": "Prompt", "id": "Prompt-ajhmq", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -1362,7 +1436,9 @@ "documentation": "", "edited": false, "error": null, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "full_path": null, "icon": "braces", @@ -1387,7 +1463,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1436,7 +1514,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1481,7 +1561,9 @@ "display_name": "Prompt", "id": "Prompt-6JL4E", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { @@ -1492,7 +1574,9 @@ "documentation": "", "edited": false, "error": null, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "full_path": null, "icon": "braces", @@ -1517,7 +1601,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1566,7 +1652,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1611,18 +1699,25 @@ "display_name": "Prompt", "id": "Prompt-WvveL", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["research_agent_output", "finance_agent_output"] + "template": [ + "research_agent_output", + "finance_agent_output" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, "error": null, - "field_order": ["template"], + "field_order": [ + "template" + ], "frozen": false, "full_path": null, "icon": "braces", @@ -1647,7 +1742,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1680,7 +1777,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1700,7 +1800,10 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message", "Text"], + "input_types": [ + "Message", + "Text" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1736,7 +1839,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "tool_placeholder", @@ -1779,7 +1884,9 @@ "data": { "id": "ChatInput-NuUHZ", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1817,7 +1924,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1830,7 +1939,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "background_color", @@ -1850,7 +1961,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "chat_icon", @@ -1954,7 +2067,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "placeholder": "", "required": false, "show": true, @@ -1970,7 +2086,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "sender_name", @@ -1990,7 +2108,9 @@ "display_name": "Session ID", "dynamic": false, "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "session_id", @@ -2026,7 +2146,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "name": "text_color", @@ -2106,7 +2228,9 @@ "display_name": "Researcher Agent", "id": "Agent-b7nmW", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -2155,7 +2279,9 @@ "required_inputs": null, "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -2187,7 +2313,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2308,7 +2436,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2516,7 +2646,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2584,7 +2716,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "list_add_label": "Add More", "name": "tools", @@ -2645,7 +2779,11 @@ "display_name": "Yahoo! Finance", "id": "YfinanceComponent-hAneS", "node": { - "base_classes": ["Data", "DataFrame", "Message"], + "base_classes": [ + "Data", + "DataFrame", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -2653,7 +2791,11 @@ "display_name": "Yahoo! Finance", "documentation": "", "edited": false, - "field_order": ["symbol", "method", "num_news"], + "field_order": [ + "symbol", + "method", + "num_news" + ], "frozen": false, "icon": "trending-up", "legacy": false, @@ -2676,7 +2818,9 @@ "required_inputs": null, "selected": "Tool", "tool_mode": true, - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -2771,7 +2915,9 @@ "display_name": "Stock Symbol", "dynamic": false, "info": "The stock symbol to retrieve data for (e.g., AAPL, GOOG).", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2819,7 +2965,9 @@ "name": "fetch_content_dataframe", "readonly": false, "status": true, - "tags": ["fetch_content_dataframe"] + "tags": [ + "fetch_content_dataframe" + ] } ] } @@ -2847,7 +2995,9 @@ "data": { "id": "CalculatorComponent-0P2yI", "node": { - "base_classes": ["Data"], + "base_classes": [ + "Data" + ], "beta": false, "category": "tools", "conditional_paths": [], @@ -2856,7 +3006,9 @@ "display_name": "Calculator", "documentation": "", "edited": false, - "field_order": ["expression"], + "field_order": [ + "expression" + ], "frozen": false, "icon": "calculator", "key": "CalculatorComponent", @@ -2880,7 +3032,9 @@ "required_inputs": null, "selected": "Tool", "tool_mode": true, - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -2912,7 +3066,9 @@ "display_name": "Expression", "dynamic": false, "info": "The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2960,7 +3116,9 @@ "name": "evaluate_expression", "readonly": false, "status": true, - "tags": ["evaluate_expression"] + "tags": [ + "evaluate_expression" + ] } ] } @@ -2988,7 +3146,10 @@ "data": { "id": "TavilySearchComponent-DTUmi", "node": { - "base_classes": ["Data", "Message"], + "base_classes": [ + "Data", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -3028,7 +3189,9 @@ "required_inputs": null, "selected": "Tool", "tool_mode": true, - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -3112,7 +3275,9 @@ "display_name": "Exclude Domains", "dynamic": false, "info": "Comma-separated list of domains to exclude from the search results.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3151,7 +3316,9 @@ "display_name": "Include Domains", "dynamic": false, "info": "Comma-separated list of domains to include in the search results.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3226,7 +3393,9 @@ "display_name": "Search Query", "dynamic": false, "info": "The search query you want to execute with Tavily.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3250,7 +3419,10 @@ "dynamic": false, "info": "The depth of the search.", "name": "search_depth", - "options": ["basic", "advanced"], + "options": [ + "basic", + "advanced" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -3270,7 +3442,12 @@ "dynamic": false, "info": "The time range back from the current date to filter results.", "name": "time_range", - "options": ["day", "week", "month", "year"], + "options": [ + "day", + "week", + "month", + "year" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -3313,7 +3490,9 @@ "name": "fetch_content_dataframe", "readonly": false, "status": true, - "tags": ["fetch_content_dataframe"] + "tags": [ + "fetch_content_dataframe" + ] } ] }, @@ -3326,7 +3505,10 @@ "dynamic": false, "info": "The category of the search.", "name": "topic", - "options": ["general", "news"], + "options": [ + "general", + "news" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -3361,7 +3543,9 @@ "data": { "id": "ChatOutput-gbqPo", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "category": "outputs", "conditional_paths": [], @@ -3402,7 +3586,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -3416,7 +3602,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3437,7 +3625,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3494,7 +3684,9 @@ "display_name": "Data Template", "dynamic": false, "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3515,7 +3707,11 @@ "display_name": "Inputs", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Data", "DataFrame", "Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "list_add_label": "Add More", "name": "input_value", @@ -3536,7 +3732,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -3553,7 +3752,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3574,7 +3775,9 @@ "display_name": "Session ID", "dynamic": false, "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3613,7 +3816,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -3660,5 +3865,9 @@ "is_component": false, "last_tested_version": "1.4.3", "name": "Sequential Tasks Agents", - "tags": ["assistants", "agents", "web-scraping"] -} + "tags": [ + "assistants", + "agents", + "web-scraping" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json index fa8b74256..1ec103133 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json @@ -9,12 +9,16 @@ "dataType": "YouTubeCommentsComponent", "id": "YouTubeCommentsComponent-y3wJZ", "name": "comments", - "output_types": ["DataFrame"] + "output_types": [ + "DataFrame" + ] }, "targetHandle": { "fieldName": "df", "id": "BatchRunComponent-30WdR", - "inputTypes": ["DataFrame"], + "inputTypes": [ + "DataFrame" + ], "type": "other" } }, @@ -33,12 +37,16 @@ "dataType": "Prompt", "id": "Prompt-yqoLt", "name": "prompt", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "Agent-JRSRu", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -57,12 +65,18 @@ "dataType": "Agent", "id": "Agent-JRSRu", "name": "response", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "input_value", "id": "ChatOutput-vlskP", - "inputTypes": ["Data", "DataFrame", "Message"], + "inputTypes": [ + "Data", + "DataFrame", + "Message" + ], "type": "str" } }, @@ -81,12 +95,16 @@ "dataType": "YouTubeTranscripts", "id": "YouTubeTranscripts-TlFcG", "name": "component_as_tool", - "output_types": ["Tool"] + "output_types": [ + "Tool" + ] }, "targetHandle": { "fieldName": "tools", "id": "Agent-JRSRu", - "inputTypes": ["Tool"], + "inputTypes": [ + "Tool" + ], "type": "other" } }, @@ -105,12 +123,17 @@ "dataType": "BatchRunComponent", "id": "BatchRunComponent-30WdR", "name": "batch_results", - "output_types": ["DataFrame"] + "output_types": [ + "DataFrame" + ] }, "targetHandle": { "fieldName": "input_data", "id": "parser-k0Bpy", - "inputTypes": ["DataFrame", "Data"], + "inputTypes": [ + "DataFrame", + "Data" + ], "type": "other" } }, @@ -129,12 +152,16 @@ "dataType": "parser", "id": "parser-k0Bpy", "name": "parsed_text", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "analysis", "id": "Prompt-yqoLt", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -153,12 +180,16 @@ "dataType": "LanguageModelComponent", "id": "LanguageModelComponent-OvIt5", "name": "model_output", - "output_types": ["LanguageModel"] + "output_types": [ + "LanguageModel" + ] }, "targetHandle": { "fieldName": "model", "id": "BatchRunComponent-30WdR", - "inputTypes": ["LanguageModel"], + "inputTypes": [ + "LanguageModel" + ], "type": "other" } }, @@ -177,12 +208,16 @@ "dataType": "ChatInput", "id": "ChatInput-kaWcL", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "video_url", "id": "YouTubeCommentsComponent-y3wJZ", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -201,12 +236,16 @@ "dataType": "ChatInput", "id": "ChatInput-kaWcL", "name": "message", - "output_types": ["Message"] + "output_types": [ + "Message" + ] }, "targetHandle": { "fieldName": "url", "id": "Prompt-yqoLt", - "inputTypes": ["Message"], + "inputTypes": [ + "Message" + ], "type": "str" } }, @@ -223,7 +262,9 @@ "data": { "id": "BatchRunComponent-30WdR", "node": { - "base_classes": ["DataFrame"], + "base_classes": [ + "DataFrame" + ], "beta": false, "category": "helpers", "conditional_paths": [], @@ -232,7 +273,12 @@ "display_name": "Batch Run", "documentation": "", "edited": false, - "field_order": ["model", "system_message", "df", "column_name"], + "field_order": [ + "model", + "system_message", + "df", + "column_name" + ], "frozen": false, "icon": "List", "key": "BatchRunComponent", @@ -254,7 +300,9 @@ "name": "batch_results", "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -305,7 +353,9 @@ "display_name": "DataFrame", "dynamic": false, "info": "The DataFrame whose column (specified by 'column_name') we'll treat as text messages.", - "input_types": ["DataFrame"], + "input_types": [ + "DataFrame" + ], "list": false, "list_add_label": "Add More", "name": "df", @@ -343,7 +393,9 @@ "display_name": "Language Model", "dynamic": false, "info": "Connect the 'Language Model' output from your LLM component here.", - "input_types": ["LanguageModel"], + "input_types": [ + "LanguageModel" + ], "list": false, "list_add_label": "Add More", "name": "model", @@ -361,7 +413,9 @@ "display_name": "Output Column Name", "dynamic": false, "info": "Name of the column where the model's response will be stored.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -382,7 +436,9 @@ "display_name": "Instructions", "dynamic": false, "info": "Multi-line system instruction for all rows in the DataFrame.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -422,7 +478,9 @@ "data": { "id": "YouTubeCommentsComponent-y3wJZ", "node": { - "base_classes": ["DataFrame"], + "base_classes": [ + "DataFrame" + ], "beta": false, "category": "youtube", "conditional_paths": [], @@ -460,7 +518,9 @@ "name": "comments", "selected": "DataFrame", "tool_mode": true, - "types": ["DataFrame"], + "types": [ + "DataFrame" + ], "value": "__UNDEFINED__" } ], @@ -566,7 +626,10 @@ "dynamic": false, "info": "Sort comments by time or relevance.", "name": "sort_by", - "options": ["time", "relevance"], + "options": [ + "time", + "relevance" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -583,7 +646,9 @@ "display_name": "Video URL", "dynamic": false, "info": "The URL of the YouTube video to get comments from.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -622,7 +687,9 @@ "data": { "id": "Agent-JRSRu", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -673,7 +740,9 @@ "required_inputs": null, "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -705,7 +774,9 @@ "display_name": "Agent Description [Deprecated]", "dynamic": false, "info": "The description of the agent. This is only used when in Tool Mode. Defaults to 'A helpful assistant with access to the following tools:' and tools are added dynamically. This feature is deprecated and will be removed in future versions.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -826,7 +897,9 @@ "display_name": "Input", "dynamic": false, "info": "The input provided by the user for the agent to process.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1034,7 +1107,9 @@ "display_name": "Agent Instructions", "dynamic": false, "info": "System Prompt: Initial instructions and context provided to guide the agent's behavior.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1102,7 +1177,9 @@ "display_name": "Tools", "dynamic": false, "info": "These are the tools that the agent can use to help with tasks.", - "input_types": ["Tool"], + "input_types": [ + "Tool" + ], "list": true, "list_add_label": "Add More", "name": "tools", @@ -1156,18 +1233,26 @@ "data": { "id": "Prompt-yqoLt", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": { - "template": ["url", "analysis"] + "template": [ + "url", + "analysis" + ] }, "description": "Create a prompt template with dynamic variables.", "display_name": "Prompt", "documentation": "", "edited": false, "error": null, - "field_order": ["template", "tool_placeholder"], + "field_order": [ + "template", + "tool_placeholder" + ], "frozen": false, "full_path": null, "icon": "braces", @@ -1190,7 +1275,9 @@ "name": "prompt", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1205,7 +1292,9 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1259,7 +1348,9 @@ "display_name": "Tool Placeholder", "dynamic": false, "info": "A placeholder input for tool mode.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1282,7 +1373,9 @@ "fileTypes": [], "file_path": "", "info": "", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "load_from_db": false, "multiline": true, @@ -1318,7 +1411,9 @@ "data": { "id": "ChatOutput-vlskP", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "category": "outputs", "conditional_paths": [], @@ -1359,7 +1454,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1373,7 +1470,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1394,7 +1493,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1451,7 +1552,9 @@ "display_name": "Data Template", "dynamic": false, "info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1472,7 +1575,11 @@ "display_name": "Inputs", "dynamic": false, "info": "Message to be passed as output.", - "input_types": ["Data", "DataFrame", "Message"], + "input_types": [ + "Data", + "DataFrame", + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1496,7 +1603,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -1513,7 +1623,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1534,7 +1646,9 @@ "display_name": "Session ID", "dynamic": false, "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1573,7 +1687,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1611,7 +1727,11 @@ "data": { "id": "YouTubeTranscripts-TlFcG", "node": { - "base_classes": ["Data", "DataFrame", "Message"], + "base_classes": [ + "Data", + "DataFrame", + "Message" + ], "beta": false, "conditional_paths": [], "custom_fields": {}, @@ -1619,7 +1739,11 @@ "display_name": "YouTube Transcripts", "documentation": "", "edited": false, - "field_order": ["url", "chunk_size_seconds", "translation"], + "field_order": [ + "url", + "chunk_size_seconds", + "translation" + ], "frozen": false, "icon": "YouTube", "last_updated": "2025-07-07T14:52:15.000Z", @@ -1644,7 +1768,9 @@ "required_inputs": null, "selected": "Tool", "tool_mode": true, - "types": ["Tool"], + "types": [ + "Tool" + ], "value": "__UNDEFINED__" } ], @@ -1719,7 +1845,9 @@ "name": "get_dataframe_output", "readonly": false, "status": true, - "tags": ["get_dataframe_output"] + "tags": [ + "get_dataframe_output" + ] }, { "args": { @@ -1735,7 +1863,9 @@ "name": "get_message_output", "readonly": false, "status": true, - "tags": ["get_message_output"] + "tags": [ + "get_message_output" + ] }, { "args": { @@ -1751,7 +1881,9 @@ "name": "get_data_output", "readonly": false, "status": true, - "tags": ["get_data_output"] + "tags": [ + "get_data_output" + ] } ] }, @@ -1795,7 +1927,9 @@ "display_name": "Video URL", "dynamic": false, "info": "Enter the YouTube video URL to get transcripts from.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1864,7 +1998,9 @@ "data": { "id": "parser-k0Bpy", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "category": "processing", "conditional_paths": [], @@ -1873,7 +2009,12 @@ "display_name": "Parser", "documentation": "", "edited": false, - "field_order": ["mode", "pattern", "input_data", "sep"], + "field_order": [ + "mode", + "pattern", + "input_data", + "sep" + ], "frozen": false, "icon": "braces", "key": "parser", @@ -1891,7 +2032,9 @@ "name": "parsed_text", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -1923,7 +2066,10 @@ "display_name": "Data or DataFrame", "dynamic": false, "info": "Accepts either a DataFrame or a Data object.", - "input_types": ["DataFrame", "Data"], + "input_types": [ + "DataFrame", + "Data" + ], "list": false, "list_add_label": "Add More", "name": "input_data", @@ -1942,7 +2088,10 @@ "dynamic": false, "info": "Convert into raw string instead of using a template.", "name": "mode", - "options": ["Parser", "Stringify"], + "options": [ + "Parser", + "Stringify" + ], "placeholder": "", "real_time_refresh": true, "required": false, @@ -1960,7 +2109,9 @@ "display_name": "Template", "dynamic": true, "info": "Use variables within curly brackets to extract column values for DataFrames or key values for Data.For example: `Name: {Name}, Age: {Age}, Country: {Country}`", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -1982,7 +2133,9 @@ "display_name": "Separator", "dynamic": false, "info": "String used to separate rows/items.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2021,7 +2174,10 @@ "data": { "id": "LanguageModelComponent-OvIt5", "node": { - "base_classes": ["LanguageModel", "Message"], + "base_classes": [ + "LanguageModel", + "Message" + ], "beta": false, "category": "models", "conditional_paths": [], @@ -2067,7 +2223,9 @@ "required_inputs": null, "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" }, { @@ -2081,7 +2239,9 @@ "required_inputs": null, "selected": "LanguageModel", "tool_mode": true, - "types": ["LanguageModel"], + "types": [ + "LanguageModel" + ], "value": "__UNDEFINED__" } ], @@ -2132,7 +2292,9 @@ "display_name": "Input", "dynamic": false, "info": "The input text to send to the model", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2188,7 +2350,11 @@ "dynamic": false, "info": "Select the model provider", "name": "provider", - "options": ["OpenAI", "Anthropic", "Google"], + "options": [ + "OpenAI", + "Anthropic", + "Google" + ], "options_metadata": [ { "icon": "OpenAI" @@ -2236,7 +2402,9 @@ "display_name": "System Message", "dynamic": false, "info": "A system message that helps set the behavior of the assistant", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2304,7 +2472,9 @@ "data": { "id": "ChatInput-kaWcL", "node": { - "base_classes": ["Message"], + "base_classes": [ + "Message" + ], "beta": false, "category": "input_output", "conditional_paths": [], @@ -2345,7 +2515,9 @@ "name": "message", "selected": "Message", "tool_mode": true, - "types": ["Message"], + "types": [ + "Message" + ], "value": "__UNDEFINED__" } ], @@ -2359,7 +2531,9 @@ "display_name": "Background Color", "dynamic": false, "info": "The background color of the icon.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2380,7 +2554,9 @@ "display_name": "Icon", "dynamic": false, "info": "The icon of the message.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2489,7 +2665,10 @@ "dynamic": false, "info": "Type of sender.", "name": "sender", - "options": ["Machine", "User"], + "options": [ + "Machine", + "User" + ], "options_metadata": [], "placeholder": "", "required": false, @@ -2507,7 +2686,9 @@ "display_name": "Sender Name", "dynamic": false, "info": "Name of the sender.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2528,7 +2709,9 @@ "display_name": "Session ID", "dynamic": false, "info": "The session ID of the chat. If empty, the current session ID parameter will be used.", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2567,7 +2750,9 @@ "display_name": "Text Color", "dynamic": false, "info": "The text color of the name", - "input_types": ["Message"], + "input_types": [ + "Message" + ], "list": false, "list_add_label": "Add More", "load_from_db": false, @@ -2614,5 +2799,8 @@ "is_component": false, "last_tested_version": "1.4.3", "name": "YouTube Analysis", - "tags": ["agents", "assistants"] -} + "tags": [ + "agents", + "assistants" + ] +} \ No newline at end of file diff --git a/src/backend/base/langflow/utils/template_validation.py b/src/backend/base/langflow/utils/template_validation.py index ac5b523b8..17de74231 100644 --- a/src/backend/base/langflow/utils/template_validation.py +++ b/src/backend/base/langflow/utils/template_validation.py @@ -155,9 +155,7 @@ async def validate_flow_execution( try: # Create a flow from the template with timeout - create_response = await asyncio.wait_for( - client.post("api/v1/flows/", json=template_data, headers=headers), timeout=10.0 - ) + create_response = await client.post("api/v1/flows/", json=template_data, headers=headers, timeout=10) if create_response.status_code != 201: # noqa: PLR2004 errors.append(f"{filename}: Failed to create flow: {create_response.status_code}") @@ -167,9 +165,7 @@ async def validate_flow_execution( try: # Build the flow with timeout - build_response = await asyncio.wait_for( - client.post(f"api/v1/build/{flow_id}/flow", json={}, headers=headers), timeout=15.0 - ) + build_response = await client.post(f"api/v1/build/{flow_id}/flow", json={}, headers=headers, timeout=10) if build_response.status_code != 200: # noqa: PLR2004 errors.append(f"{filename}: Failed to build flow: {build_response.status_code}") @@ -179,9 +175,7 @@ async def validate_flow_execution( # Get build events to validate execution events_headers = {**headers, "Accept": "application/x-ndjson"} - events_response = await asyncio.wait_for( - client.get(f"api/v1/build/{job_id}/events", headers=events_headers), timeout=10.0 - ) + events_response = await client.get(f"api/v1/build/{job_id}/events", headers=events_headers, timeout=10) if events_response.status_code != 200: # noqa: PLR2004 errors.append(f"{filename}: Failed to get build events: {events_response.status_code}") @@ -193,7 +187,7 @@ async def validate_flow_execution( finally: # Clean up the flow with timeout try: # noqa: SIM105 - await asyncio.wait_for(client.delete(f"api/v1/flows/{flow_id}", headers=headers), timeout=5.0) + await client.delete(f"api/v1/flows/{flow_id}", headers=headers, timeout=10) except asyncio.TimeoutError: # Log but don't fail if cleanup times out pass diff --git a/src/backend/tests/conftest.py b/src/backend/tests/conftest.py index a144d52a7..c156442b6 100644 --- a/src/backend/tests/conftest.py +++ b/src/backend/tests/conftest.py @@ -168,11 +168,11 @@ async def _delete_transactions_and_vertex_builds(session, flows: list[Flow]): continue try: await delete_vertex_builds_by_flow_id(session, flow_id) - except Exception as e: # noqa: BLE001 + except Exception as e: logger.debug(f"Error deleting vertex builds for flow {flow_id}: {e}") try: await delete_transactions_by_flow_id(session, flow_id) - except Exception as e: # noqa: BLE001 + except Exception as e: logger.debug(f"Error deleting transactions for flow {flow_id}: {e}") @@ -474,7 +474,7 @@ async def active_user(client): # noqa: ARG001 user = await session.get(User, user.id, options=[selectinload(User.flows)]) await _delete_transactions_and_vertex_builds(session, user.flows) await session.commit() - except Exception as e: # noqa: BLE001 + except Exception as e: logger.exception(f"Error deleting transactions and vertex builds for user: {e}") try: @@ -482,7 +482,7 @@ async def active_user(client): # noqa: ARG001 user = await session.get(User, user.id) await session.delete(user) await session.commit() - except Exception as e: # noqa: BLE001 + except Exception as e: logger.exception(f"Error deleting user: {e}") diff --git a/src/backend/tests/integration/components/astra/test_astra_component.py b/src/backend/tests/integration/components/astra/test_astra_component.py index c324b0d1b..b8c7da3dc 100644 --- a/src/backend/tests/integration/components/astra/test_astra_component.py +++ b/src/backend/tests/integration/components/astra/test_astra_component.py @@ -39,7 +39,7 @@ def astradb_client(): for collection in ALL_COLLECTIONS: try: # noqa: SIM105 client.drop_collection(collection) - except Exception: # noqa: BLE001, S110 + except Exception: # noqa: S110 pass diff --git a/src/backend/tests/locust/locustfile.py b/src/backend/tests/locust/locustfile.py index 6d77bc396..ab4cd612a 100644 --- a/src/backend/tests/locust/locustfile.py +++ b/src/backend/tests/locust/locustfile.py @@ -119,7 +119,7 @@ class FlowRunUser(FastHttpUser): error_msg = f"Unexpected status code: {response.status_code}, Response: {error_text[:200]}" response.failure(error_msg) self.log_error(endpoint, Exception(error_msg), response_time) - except Exception as e: # noqa: BLE001 + except Exception as e: response_time = (time.time() - start_time) * 1000 self.log_error(endpoint, e, response_time) response.failure(f"Error: {e}") diff --git a/src/backend/tests/unit/components/agents/test_agent_component.py b/src/backend/tests/unit/components/agents/test_agent_component.py index b028c3a4d..acaa53029 100644 --- a/src/backend/tests/unit/components/agents/test_agent_component.py +++ b/src/backend/tests/unit/components/agents/test_agent_component.py @@ -330,7 +330,7 @@ class TestAgentComponentWithClient(ComponentTestBaseWithClient): if "4" not in response_text: failed_models[model_name] = f"Expected '4' in response but got: {response_text}" - except Exception as e: # noqa: BLE001 + except Exception as e: failed_models[model_name] = f"Exception occurred: {e!s}" assert not failed_models, "The following models failed the test:\n" + "\n".join( diff --git a/src/backend/tests/unit/template/test_starter_projects.py b/src/backend/tests/unit/template/test_starter_projects.py index 2ad939267..7ea56e032 100644 --- a/src/backend/tests/unit/template/test_starter_projects.py +++ b/src/backend/tests/unit/template/test_starter_projects.py @@ -9,7 +9,6 @@ Tests all JSON templates in the starter_projects folder to ensure they: Validates that templates work correctly and prevent unexpected breakage. """ -import asyncio import json from pathlib import Path @@ -28,6 +27,24 @@ def get_starter_projects_path() -> Path: return Path("src/backend/base/langflow/initial_setup/starter_projects") +def get_template_files(): + """Get all template files for parameterization.""" + return list(get_starter_projects_path().glob("*.json")) + + +def get_basic_template_files(): + """Get basic template files for parameterization.""" + path = get_starter_projects_path() + basic_templates = ["Basic Prompting.json", "Basic Prompt Chaining.json"] + return [path / name for name in basic_templates if (path / name).exists()] + + +@pytest.fixture(autouse=True) +def disable_tracing(monkeypatch): + """Disable tracing for all template tests.""" + monkeypatch.setenv("LANGFLOW_DEACTIVATE_TRACING", "true") + + class TestStarterProjects: """Test all starter project templates.""" @@ -36,129 +53,80 @@ class TestStarterProjects: path = get_starter_projects_path() assert path.exists(), f"Directory not found: {path}" - templates = list(path.glob("*.json")) + templates = get_template_files() assert len(templates) > 0, "No template files found" - def test_all_templates_valid_json(self): - """Test all templates are valid JSON.""" - path = get_starter_projects_path() - templates = list(path.glob("*.json")) + @pytest.mark.parametrize("template_file", get_template_files(), ids=lambda x: x.name) + def test_template_valid_json(self, template_file): + """Test template is valid JSON.""" + with template_file.open(encoding="utf-8") as f: + try: + json.load(f) + except json.JSONDecodeError as e: + pytest.fail(f"Invalid JSON in {template_file.name}: {e}") - for template_file in templates: - with template_file.open(encoding="utf-8") as f: - try: - json.load(f) - except json.JSONDecodeError as e: - pytest.fail(f"Invalid JSON in {template_file.name}: {e}") + @pytest.mark.parametrize("template_file", get_template_files(), ids=lambda x: x.name) + def test_template_structure(self, template_file): + """Test template has required structure.""" + with template_file.open(encoding="utf-8") as f: + template_data = json.load(f) - def test_all_templates_structure(self): - """Test all templates have required structure.""" - path = get_starter_projects_path() - templates = list(path.glob("*.json")) + errors = validate_template_structure(template_data, template_file.name) + if errors: + error_msg = "\n".join(errors) + pytest.fail(f"Template structure errors in {template_file.name}:\n{error_msg}") - all_errors = [] - for template_file in templates: - with template_file.open(encoding="utf-8") as f: - template_data = json.load(f) + @pytest.mark.parametrize("template_file", get_template_files(), ids=lambda x: x.name) + def test_template_can_build_flow(self, template_file): + """Test template can be built into working flow.""" + with template_file.open(encoding="utf-8") as f: + template_data = json.load(f) - errors = validate_template_structure(template_data, template_file.name) - all_errors.extend(errors) - - if all_errors: - error_msg = "\n".join(all_errors) - pytest.fail(f"Template structure errors:\n{error_msg}") - - def test_all_templates_can_build_flow(self): - """Test all templates can be built into working flows.""" - path = get_starter_projects_path() - templates = list(path.glob("*.json")) - - all_errors = [] - for template_file in templates: - with template_file.open(encoding="utf-8") as f: - template_data = json.load(f) - - errors = validate_flow_can_build(template_data, template_file.name) - all_errors.extend(errors) - - if all_errors: - error_msg = "\n".join(all_errors) - pytest.fail(f"Flow build errors:\n{error_msg}") + errors = validate_flow_can_build(template_data, template_file.name) + if errors: + error_msg = "\n".join(errors) + pytest.fail(f"Flow build errors in {template_file.name}:\n{error_msg}") @pytest.mark.asyncio - async def test_all_templates_validate_endpoint(self, client, logged_in_headers): - """Test all templates using the validate endpoint.""" - path = get_starter_projects_path() - templates = list(path.glob("*.json")) + @pytest.mark.parametrize("template_file", get_template_files(), ids=lambda x: x.name) + async def test_template_validate_endpoint(self, template_file, client, logged_in_headers): + """Test template using the validate endpoint.""" + with template_file.open(encoding="utf-8") as f: + template_data = json.load(f) - all_errors = [] - for template_file in templates: + errors = await validate_flow_execution(client, template_data, template_file.name, logged_in_headers) + if errors: + error_msg = "\n".join(errors) + pytest.fail(f"Endpoint validation errors in {template_file.name}:\n{error_msg}") + + @pytest.mark.asyncio + @pytest.mark.parametrize("template_file", get_template_files(), ids=lambda x: x.name) + async def test_template_flow_execution(self, template_file, client, logged_in_headers): + """Test template can execute successfully.""" + try: with template_file.open(encoding="utf-8") as f: template_data = json.load(f) errors = await validate_flow_execution(client, template_data, template_file.name, logged_in_headers) - all_errors.extend(errors) + if errors: + error_msg = "\n".join(errors) + pytest.fail(f"Template execution errors in {template_file.name}:\n{error_msg}") - if all_errors: - error_msg = "\n".join(all_errors) - pytest.fail(f"Endpoint validation errors:\n{error_msg}") + except (ValueError, TypeError, KeyError, AttributeError, OSError, json.JSONDecodeError) as e: + pytest.fail(f"{template_file.name}: Unexpected error during validation: {e!s}") @pytest.mark.asyncio - async def test_all_templates_flow_execution(self, client, logged_in_headers): - """Test all templates can execute successfully.""" - path = get_starter_projects_path() - templates = list(path.glob("*.json")) + @pytest.mark.parametrize("template_file", get_basic_template_files(), ids=lambda x: x.name) + async def test_basic_template_flow_execution(self, template_file, client, logged_in_headers): + """Test basic template can execute successfully.""" + try: + with template_file.open(encoding="utf-8") as f: + template_data = json.load(f) - all_errors = [] + errors = await validate_flow_execution(client, template_data, template_file.name, logged_in_headers) + if errors: + error_msg = "\n".join(errors) + pytest.fail(f"Basic template execution errors in {template_file.name}:\n{error_msg}") - # Process templates in chunks to avoid timeout issues - chunk_size = 5 - template_chunks = [templates[i : i + chunk_size] for i in range(0, len(templates), chunk_size)] - - for chunk in template_chunks: - for template_file in chunk: - try: - with template_file.open(encoding="utf-8") as f: - template_data = json.load(f) - - errors = await validate_flow_execution(client, template_data, template_file.name, logged_in_headers) - all_errors.extend(errors) - - except (ValueError, TypeError, KeyError, AttributeError, OSError, json.JSONDecodeError) as e: - error_msg = f"{template_file.name}: Unexpected error during validation: {e!s}" - all_errors.append(error_msg) - - # Brief pause between chunks to avoid overwhelming the system - await asyncio.sleep(0.5) - - # All templates must pass - no failures allowed - if all_errors: - error_msg = "\n".join(all_errors) - pytest.fail(f"Template execution errors:\n{error_msg}") - - @pytest.mark.asyncio - async def test_basic_templates_flow_execution(self, client, logged_in_headers): - """Test basic templates can execute successfully.""" - path = get_starter_projects_path() - - # Only test basic templates that should reliably work - basic_templates = ["Basic Prompting.json", "Basic Prompt Chaining.json"] - - all_errors = [] - for template_name in basic_templates: - template_file = path / template_name - if template_file.exists(): - try: - with template_file.open(encoding="utf-8") as f: - template_data = json.load(f) - - errors = await validate_flow_execution(client, template_data, template_name, logged_in_headers) - all_errors.extend(errors) - - except (ValueError, TypeError, KeyError, AttributeError, OSError, json.JSONDecodeError) as e: - all_errors.append(f"{template_name}: Unexpected error during validation: {e!s}") - - # All basic templates must pass - no failures allowed - if all_errors: - error_msg = "\n".join(all_errors) - pytest.fail(f"Basic template execution errors:\n{error_msg}") + except (ValueError, TypeError, KeyError, AttributeError, OSError, json.JSONDecodeError) as e: + pytest.fail(f"{template_file.name}: Unexpected error during validation: {e!s}") diff --git a/src/backend/tests/unit/utils/test_template_validation.py b/src/backend/tests/unit/utils/test_template_validation.py index 86615bab2..0ef3f859d 100644 --- a/src/backend/tests/unit/utils/test_template_validation.py +++ b/src/backend/tests/unit/utils/test_template_validation.py @@ -464,14 +464,14 @@ class TestValidateFlowExecution: mock_client.delete.return_value = Mock() template_data = {"nodes": [], "edges": []} - headers = {"Authorization": "Bearer token"} + headers = {"Authorization": "Bearer token", "timeout": 10} errors = await validate_flow_execution(mock_client, template_data, "test.json", headers) assert len(errors) == 1 assert "Flow execution validation failed: Build error" in errors[0] # Verify cleanup was called - mock_client.delete.assert_called_once_with("api/v1/flows/flow123", headers=headers) + mock_client.delete.assert_called_once_with("api/v1/flows/flow123", headers=headers, timeout=10) class TestValidateEventStream: