From 81027f607792df573ec41294808ffe2cf2c48e2d Mon Sep 17 00:00:00 2001 From: GuanMu Date: Wed, 16 Jul 2025 02:28:57 +0000 Subject: [PATCH] Test:Add multiple test cases to verify the correctness of different output configurations, including complex outputs and handling of missing variables. --- .../workflow/graph_engine/graph_engine.py | 8 +- .../core/workflow/nodes/answer/test_answer.py | 371 ++++++++++++++++++ 2 files changed, 378 insertions(+), 1 deletion(-) diff --git a/api/core/workflow/graph_engine/graph_engine.py b/api/core/workflow/graph_engine/graph_engine.py index 4e552981ae..c73ba46e0d 100644 --- a/api/core/workflow/graph_engine/graph_engine.py +++ b/api/core/workflow/graph_engine/graph_engine.py @@ -196,7 +196,13 @@ class GraphEngine: self.graph_runtime_state.outputs["answer"] = self.graph_runtime_state.outputs[ "answer" ].strip() - self.graph_runtime_state.outputs["outputs"] = item.route_node_state.node_run_result.outputs + + # Only save the user-defined output variables, not the entire node output + if (item.route_node_state.node_run_result + and item.route_node_state.node_run_result.outputs + and "outputs" in item.route_node_state.node_run_result.outputs): + user_outputs = item.route_node_state.node_run_result.outputs.get("outputs", {}) + self.graph_runtime_state.outputs.update(user_outputs) except Exception as e: logger.exception("Graph run failed") yield GraphRunFailedEvent(error=str(e), exceptions_count=len(handle_exceptions)) diff --git a/api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py b/api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py index 860bc29455..39adf1da63 100644 --- a/api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py +++ b/api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py @@ -82,3 +82,374 @@ def test_execute_answer(): assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED assert result.outputs["answer"] == "Today's weather is sunny\nYou are a helpful AI.\n{{img}}\nFin." + + +def test_execute_answer_with_outputs(): + """Test Answer node with custom output variables""" + graph_config = { + "edges": [ + { + "id": "start-source-answer-target", + "source": "start", + "target": "answer", + }, + ], + "nodes": [ + {"data": {"type": "start"}, "id": "start"}, + { + "data": { + "title": "Answer with outputs", + "type": "answer", + "answer": "Weather: {{#start.weather#}}, Score: {{#start.score#}}", + "outputs": [ + { + "variable": "confidence", + "type": "number", + "value_selector": ["start", "score"] + }, + { + "variable": "status", + "type": "string", + "value_selector": ["start", "weather"] + } + ], + }, + "id": "answer", + }, + ], + } + + graph = Graph.init(graph_config=graph_config) + + init_params = GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config=graph_config, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.DEBUGGER, + call_depth=0, + ) + + # construct variable pool + variable_pool = VariablePool( + system_variables={SystemVariableKey.FILES: [], SystemVariableKey.USER_ID: "aaa"}, + user_inputs={}, + environment_variables=[], + conversation_variables=[], + ) + variable_pool.add(["start", "weather"], "sunny") + variable_pool.add(["start", "score"], 85) + + node = AnswerNode( + id=str(uuid.uuid4()), + graph_init_params=init_params, + graph=graph, + graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()), + config={ + "id": "answer", + "data": { + "title": "Answer with outputs", + "type": "answer", + "answer": "Weather: {{#start.weather#}}, Score: {{#start.score#}}", + "outputs": [ + { + "variable": "confidence", + "type": "number", + "value_selector": ["start", "score"] + }, + { + "variable": "status", + "type": "string", + "value_selector": ["start", "weather"] + } + ], + }, + }, + ) + + # Mock db.session.close() + db.session.close = MagicMock() + + # execute node + result = node._run() + + assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED + assert result.outputs is not None + assert result.outputs["answer"] == "Weather: sunny, Score: 85" + + # Check outputs field + assert "outputs" in result.outputs + outputs = result.outputs["outputs"] + assert outputs["confidence"] == 85 + assert outputs["status"] == "sunny" + + +def test_execute_answer_with_complex_outputs(): + """Test Answer node with complex output variables including arrays and objects""" + graph_config = { + "edges": [ + { + "id": "start-source-answer-target", + "source": "start", + "target": "answer", + }, + ], + "nodes": [ + {"data": {"type": "start"}, "id": "start"}, + { + "data": { + "title": "Complex outputs", + "type": "answer", + "answer": "Analysis complete", + "outputs": [ + { + "variable": "scores", + "type": "array[number]", + "value_selector": ["start", "score_list"] + }, + { + "variable": "metadata", + "type": "object", + "value_selector": ["start", "meta_info"] + } + ], + }, + "id": "answer", + }, + ], + } + + graph = Graph.init(graph_config=graph_config) + + init_params = GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config=graph_config, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.DEBUGGER, + call_depth=0, + ) + + # construct variable pool with complex data + variable_pool = VariablePool( + system_variables={SystemVariableKey.FILES: [], SystemVariableKey.USER_ID: "aaa"}, + user_inputs={}, + environment_variables=[], + conversation_variables=[], + ) + variable_pool.add(["start", "score_list"], [85, 92, 78]) + variable_pool.add(["start", "meta_info"], {"category": "test", "priority": "high"}) + + node = AnswerNode( + id=str(uuid.uuid4()), + graph_init_params=init_params, + graph=graph, + graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()), + config={ + "id": "answer", + "data": { + "title": "Complex outputs", + "type": "answer", + "answer": "Analysis complete", + "outputs": [ + { + "variable": "scores", + "type": "array[number]", + "value_selector": ["start", "score_list"] + }, + { + "variable": "metadata", + "type": "object", + "value_selector": ["start", "meta_info"] + } + ], + }, + }, + ) + + # Mock db.session.close() + db.session.close = MagicMock() + + # execute node + result = node._run() + + assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED + assert result.outputs["answer"] == "Analysis complete" + + # Check complex outputs + assert "outputs" in result.outputs + outputs = result.outputs["outputs"] + assert outputs["scores"] == [85, 92, 78] + assert outputs["metadata"] == {"category": "test", "priority": "high"} + + +def test_execute_answer_with_empty_outputs(): + """Test Answer node with empty outputs configuration""" + graph_config = { + "edges": [ + { + "id": "start-source-answer-target", + "source": "start", + "target": "answer", + }, + ], + "nodes": [ + {"data": {"type": "start"}, "id": "start"}, + { + "data": { + "title": "No outputs", + "type": "answer", + "answer": "Simple answer", + "outputs": [], + }, + "id": "answer", + }, + ], + } + + graph = Graph.init(graph_config=graph_config) + + init_params = GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config=graph_config, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.DEBUGGER, + call_depth=0, + ) + + # construct variable pool + variable_pool = VariablePool( + system_variables={SystemVariableKey.FILES: [], SystemVariableKey.USER_ID: "aaa"}, + user_inputs={}, + environment_variables=[], + conversation_variables=[], + ) + + node = AnswerNode( + id=str(uuid.uuid4()), + graph_init_params=init_params, + graph=graph, + graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()), + config={ + "id": "answer", + "data": { + "title": "No outputs", + "type": "answer", + "answer": "Simple answer", + "outputs": [], + }, + }, + ) + + # Mock db.session.close() + db.session.close = MagicMock() + + # execute node + result = node._run() + + assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED + assert result.outputs["answer"] == "Simple answer" + + # Check that outputs field is empty when no outputs are configured + assert "outputs" in result.outputs + assert result.outputs["outputs"] == {} + + +def test_execute_answer_outputs_variable_not_found(): + """Test Answer node when output variable selector points to non-existent variable""" + graph_config = { + "edges": [ + { + "id": "start-source-answer-target", + "source": "start", + "target": "answer", + }, + ], + "nodes": [ + {"data": {"type": "start"}, "id": "start"}, + { + "data": { + "title": "Missing variable", + "type": "answer", + "answer": "Test answer", + "outputs": [ + { + "variable": "missing_var", + "type": "string", + "value_selector": ["start", "non_existent"] + } + ], + }, + "id": "answer", + }, + ], + } + + graph = Graph.init(graph_config=graph_config) + + init_params = GraphInitParams( + tenant_id="1", + app_id="1", + workflow_type=WorkflowType.WORKFLOW, + workflow_id="1", + graph_config=graph_config, + user_id="1", + user_from=UserFrom.ACCOUNT, + invoke_from=InvokeFrom.DEBUGGER, + call_depth=0, + ) + + # construct variable pool without the referenced variable + variable_pool = VariablePool( + system_variables={SystemVariableKey.FILES: [], SystemVariableKey.USER_ID: "aaa"}, + user_inputs={}, + environment_variables=[], + conversation_variables=[], + ) + + node = AnswerNode( + id=str(uuid.uuid4()), + graph_init_params=init_params, + graph=graph, + graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()), + config={ + "id": "answer", + "data": { + "title": "Missing variable", + "type": "answer", + "answer": "Test answer", + "outputs": [ + { + "variable": "missing_var", + "type": "string", + "value_selector": ["start", "non_existent"] + } + ], + }, + }, + ) + + # Mock db.session.close() + db.session.close = MagicMock() + + # execute node + result = node._run() + + assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED + assert result.outputs["answer"] == "Test answer" + + # Check that outputs field handles missing variables gracefully + assert "outputs" in result.outputs + outputs = result.outputs["outputs"] + # Missing variables should result in None or empty value + assert outputs.get("missing_var") is None or outputs.get("missing_var") == ""