mirror of https://github.com/langgenius/dify.git
feat: add flatten_output configuration to iteration node (#27502)
This commit is contained in:
parent
43bcf40f80
commit
b6e0abadab
|
|
@ -23,6 +23,7 @@ class IterationNodeData(BaseIterationNodeData):
|
||||||
is_parallel: bool = False # open the parallel mode or not
|
is_parallel: bool = False # open the parallel mode or not
|
||||||
parallel_nums: int = 10 # the numbers of parallel
|
parallel_nums: int = 10 # the numbers of parallel
|
||||||
error_handle_mode: ErrorHandleMode = ErrorHandleMode.TERMINATED # how to handle the error
|
error_handle_mode: ErrorHandleMode = ErrorHandleMode.TERMINATED # how to handle the error
|
||||||
|
flatten_output: bool = True # whether to flatten the output array if all elements are lists
|
||||||
|
|
||||||
|
|
||||||
class IterationStartNodeData(BaseNodeData):
|
class IterationStartNodeData(BaseNodeData):
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ class IterationNode(LLMUsageTrackingMixin, Node):
|
||||||
"is_parallel": False,
|
"is_parallel": False,
|
||||||
"parallel_nums": 10,
|
"parallel_nums": 10,
|
||||||
"error_handle_mode": ErrorHandleMode.TERMINATED,
|
"error_handle_mode": ErrorHandleMode.TERMINATED,
|
||||||
|
"flatten_output": True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -411,7 +412,14 @@ class IterationNode(LLMUsageTrackingMixin, Node):
|
||||||
"""
|
"""
|
||||||
Flatten the outputs list if all elements are lists.
|
Flatten the outputs list if all elements are lists.
|
||||||
This maintains backward compatibility with version 1.8.1 behavior.
|
This maintains backward compatibility with version 1.8.1 behavior.
|
||||||
|
|
||||||
|
If flatten_output is False, returns outputs as-is (nested structure).
|
||||||
|
If flatten_output is True (default), flattens the list if all elements are lists.
|
||||||
"""
|
"""
|
||||||
|
# If flatten_output is disabled, return outputs as-is
|
||||||
|
if not self._node_data.flatten_output:
|
||||||
|
return outputs
|
||||||
|
|
||||||
if not outputs:
|
if not outputs:
|
||||||
return outputs
|
return outputs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
app:
|
||||||
|
description: 'This workflow tests the iteration node with flatten_output=False.
|
||||||
|
|
||||||
|
|
||||||
|
It processes [1, 2, 3], outputs [item, item*2] for each iteration.
|
||||||
|
|
||||||
|
|
||||||
|
With flatten_output=False, it should output nested arrays:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
{"output": [[1, 2], [2, 4], [3, 6]]}
|
||||||
|
|
||||||
|
```'
|
||||||
|
icon: 🤖
|
||||||
|
icon_background: '#FFEAD5'
|
||||||
|
mode: workflow
|
||||||
|
name: test_iteration_flatten_disabled
|
||||||
|
use_icon_as_answer_icon: false
|
||||||
|
dependencies: []
|
||||||
|
kind: app
|
||||||
|
version: 0.3.1
|
||||||
|
workflow:
|
||||||
|
conversation_variables: []
|
||||||
|
environment_variables: []
|
||||||
|
features:
|
||||||
|
file_upload:
|
||||||
|
enabled: false
|
||||||
|
opening_statement: ''
|
||||||
|
retriever_resource:
|
||||||
|
enabled: true
|
||||||
|
sensitive_word_avoidance:
|
||||||
|
enabled: false
|
||||||
|
speech_to_text:
|
||||||
|
enabled: false
|
||||||
|
suggested_questions: []
|
||||||
|
suggested_questions_after_answer:
|
||||||
|
enabled: false
|
||||||
|
text_to_speech:
|
||||||
|
enabled: false
|
||||||
|
graph:
|
||||||
|
edges:
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: start
|
||||||
|
targetType: code
|
||||||
|
id: start-source-code-target
|
||||||
|
source: start_node
|
||||||
|
sourceHandle: source
|
||||||
|
target: code_node
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: code
|
||||||
|
targetType: iteration
|
||||||
|
id: code-source-iteration-target
|
||||||
|
source: code_node
|
||||||
|
sourceHandle: source
|
||||||
|
target: iteration_node
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: true
|
||||||
|
isInLoop: false
|
||||||
|
iteration_id: iteration_node
|
||||||
|
sourceType: iteration-start
|
||||||
|
targetType: code
|
||||||
|
id: iteration-start-source-code-inner-target
|
||||||
|
source: iteration_nodestart
|
||||||
|
sourceHandle: source
|
||||||
|
target: code_inner_node
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 1002
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: iteration
|
||||||
|
targetType: end
|
||||||
|
id: iteration-source-end-target
|
||||||
|
source: iteration_node
|
||||||
|
sourceHandle: source
|
||||||
|
target: end_node
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
nodes:
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
selected: false
|
||||||
|
title: Start
|
||||||
|
type: start
|
||||||
|
variables: []
|
||||||
|
height: 54
|
||||||
|
id: start_node
|
||||||
|
position:
|
||||||
|
x: 80
|
||||||
|
y: 282
|
||||||
|
positionAbsolute:
|
||||||
|
x: 80
|
||||||
|
y: 282
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 244
|
||||||
|
- data:
|
||||||
|
code: "\ndef main() -> dict:\n return {\n \"result\": [1, 2, 3],\n\
|
||||||
|
\ }\n"
|
||||||
|
code_language: python3
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
result:
|
||||||
|
children: null
|
||||||
|
type: array[number]
|
||||||
|
selected: false
|
||||||
|
title: Generate Array
|
||||||
|
type: code
|
||||||
|
variables: []
|
||||||
|
height: 54
|
||||||
|
id: code_node
|
||||||
|
position:
|
||||||
|
x: 384
|
||||||
|
y: 282
|
||||||
|
positionAbsolute:
|
||||||
|
x: 384
|
||||||
|
y: 282
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 244
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
error_handle_mode: terminated
|
||||||
|
flatten_output: false
|
||||||
|
height: 178
|
||||||
|
is_parallel: false
|
||||||
|
iterator_input_type: array[number]
|
||||||
|
iterator_selector:
|
||||||
|
- code_node
|
||||||
|
- result
|
||||||
|
output_selector:
|
||||||
|
- code_inner_node
|
||||||
|
- result
|
||||||
|
output_type: array[array[number]]
|
||||||
|
parallel_nums: 10
|
||||||
|
selected: false
|
||||||
|
start_node_id: iteration_nodestart
|
||||||
|
title: Iteration with Flatten Disabled
|
||||||
|
type: iteration
|
||||||
|
width: 388
|
||||||
|
height: 178
|
||||||
|
id: iteration_node
|
||||||
|
position:
|
||||||
|
x: 684
|
||||||
|
y: 282
|
||||||
|
positionAbsolute:
|
||||||
|
x: 684
|
||||||
|
y: 282
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 388
|
||||||
|
zIndex: 1
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
isInIteration: true
|
||||||
|
selected: false
|
||||||
|
title: ''
|
||||||
|
type: iteration-start
|
||||||
|
draggable: false
|
||||||
|
height: 48
|
||||||
|
id: iteration_nodestart
|
||||||
|
parentId: iteration_node
|
||||||
|
position:
|
||||||
|
x: 24
|
||||||
|
y: 68
|
||||||
|
positionAbsolute:
|
||||||
|
x: 708
|
||||||
|
y: 350
|
||||||
|
selectable: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom-iteration-start
|
||||||
|
width: 44
|
||||||
|
zIndex: 1002
|
||||||
|
- data:
|
||||||
|
code: "\ndef main(arg1: int) -> dict:\n return {\n \"result\": [arg1,\
|
||||||
|
\ arg1 * 2],\n }\n"
|
||||||
|
code_language: python3
|
||||||
|
desc: ''
|
||||||
|
isInIteration: true
|
||||||
|
isInLoop: false
|
||||||
|
iteration_id: iteration_node
|
||||||
|
outputs:
|
||||||
|
result:
|
||||||
|
children: null
|
||||||
|
type: array[number]
|
||||||
|
selected: false
|
||||||
|
title: Generate Pair
|
||||||
|
type: code
|
||||||
|
variables:
|
||||||
|
- value_selector:
|
||||||
|
- iteration_node
|
||||||
|
- item
|
||||||
|
value_type: number
|
||||||
|
variable: arg1
|
||||||
|
height: 54
|
||||||
|
id: code_inner_node
|
||||||
|
parentId: iteration_node
|
||||||
|
position:
|
||||||
|
x: 128
|
||||||
|
y: 68
|
||||||
|
positionAbsolute:
|
||||||
|
x: 812
|
||||||
|
y: 350
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 244
|
||||||
|
zIndex: 1002
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- iteration_node
|
||||||
|
- output
|
||||||
|
value_type: array[array[number]]
|
||||||
|
variable: output
|
||||||
|
selected: false
|
||||||
|
title: End
|
||||||
|
type: end
|
||||||
|
height: 90
|
||||||
|
id: end_node
|
||||||
|
position:
|
||||||
|
x: 1132
|
||||||
|
y: 282
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1132
|
||||||
|
y: 282
|
||||||
|
selected: true
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 244
|
||||||
|
viewport:
|
||||||
|
x: -476
|
||||||
|
y: 3
|
||||||
|
zoom: 1
|
||||||
|
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
app:
|
||||||
|
description: 'This workflow tests the iteration node with flatten_output=True.
|
||||||
|
|
||||||
|
|
||||||
|
It processes [1, 2, 3], outputs [item, item*2] for each iteration.
|
||||||
|
|
||||||
|
|
||||||
|
With flatten_output=True (default), it should output:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
{"output": [1, 2, 2, 4, 3, 6]}
|
||||||
|
|
||||||
|
```'
|
||||||
|
icon: 🤖
|
||||||
|
icon_background: '#FFEAD5'
|
||||||
|
mode: workflow
|
||||||
|
name: test_iteration_flatten_enabled
|
||||||
|
use_icon_as_answer_icon: false
|
||||||
|
dependencies: []
|
||||||
|
kind: app
|
||||||
|
version: 0.3.1
|
||||||
|
workflow:
|
||||||
|
conversation_variables: []
|
||||||
|
environment_variables: []
|
||||||
|
features:
|
||||||
|
file_upload:
|
||||||
|
enabled: false
|
||||||
|
opening_statement: ''
|
||||||
|
retriever_resource:
|
||||||
|
enabled: true
|
||||||
|
sensitive_word_avoidance:
|
||||||
|
enabled: false
|
||||||
|
speech_to_text:
|
||||||
|
enabled: false
|
||||||
|
suggested_questions: []
|
||||||
|
suggested_questions_after_answer:
|
||||||
|
enabled: false
|
||||||
|
text_to_speech:
|
||||||
|
enabled: false
|
||||||
|
graph:
|
||||||
|
edges:
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: start
|
||||||
|
targetType: code
|
||||||
|
id: start-source-code-target
|
||||||
|
source: start_node
|
||||||
|
sourceHandle: source
|
||||||
|
target: code_node
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: code
|
||||||
|
targetType: iteration
|
||||||
|
id: code-source-iteration-target
|
||||||
|
source: code_node
|
||||||
|
sourceHandle: source
|
||||||
|
target: iteration_node
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
- data:
|
||||||
|
isInIteration: true
|
||||||
|
isInLoop: false
|
||||||
|
iteration_id: iteration_node
|
||||||
|
sourceType: iteration-start
|
||||||
|
targetType: code
|
||||||
|
id: iteration-start-source-code-inner-target
|
||||||
|
source: iteration_nodestart
|
||||||
|
sourceHandle: source
|
||||||
|
target: code_inner_node
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 1002
|
||||||
|
- data:
|
||||||
|
isInIteration: false
|
||||||
|
isInLoop: false
|
||||||
|
sourceType: iteration
|
||||||
|
targetType: end
|
||||||
|
id: iteration-source-end-target
|
||||||
|
source: iteration_node
|
||||||
|
sourceHandle: source
|
||||||
|
target: end_node
|
||||||
|
targetHandle: target
|
||||||
|
type: custom
|
||||||
|
zIndex: 0
|
||||||
|
nodes:
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
selected: false
|
||||||
|
title: Start
|
||||||
|
type: start
|
||||||
|
variables: []
|
||||||
|
height: 54
|
||||||
|
id: start_node
|
||||||
|
position:
|
||||||
|
x: 80
|
||||||
|
y: 282
|
||||||
|
positionAbsolute:
|
||||||
|
x: 80
|
||||||
|
y: 282
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 244
|
||||||
|
- data:
|
||||||
|
code: "\ndef main() -> dict:\n return {\n \"result\": [1, 2, 3],\n\
|
||||||
|
\ }\n"
|
||||||
|
code_language: python3
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
result:
|
||||||
|
children: null
|
||||||
|
type: array[number]
|
||||||
|
selected: false
|
||||||
|
title: Generate Array
|
||||||
|
type: code
|
||||||
|
variables: []
|
||||||
|
height: 54
|
||||||
|
id: code_node
|
||||||
|
position:
|
||||||
|
x: 384
|
||||||
|
y: 282
|
||||||
|
positionAbsolute:
|
||||||
|
x: 384
|
||||||
|
y: 282
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 244
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
error_handle_mode: terminated
|
||||||
|
flatten_output: true
|
||||||
|
height: 178
|
||||||
|
is_parallel: false
|
||||||
|
iterator_input_type: array[number]
|
||||||
|
iterator_selector:
|
||||||
|
- code_node
|
||||||
|
- result
|
||||||
|
output_selector:
|
||||||
|
- code_inner_node
|
||||||
|
- result
|
||||||
|
output_type: array[array[number]]
|
||||||
|
parallel_nums: 10
|
||||||
|
selected: false
|
||||||
|
start_node_id: iteration_nodestart
|
||||||
|
title: Iteration with Flatten Enabled
|
||||||
|
type: iteration
|
||||||
|
width: 388
|
||||||
|
height: 178
|
||||||
|
id: iteration_node
|
||||||
|
position:
|
||||||
|
x: 684
|
||||||
|
y: 282
|
||||||
|
positionAbsolute:
|
||||||
|
x: 684
|
||||||
|
y: 282
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 388
|
||||||
|
zIndex: 1
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
isInIteration: true
|
||||||
|
selected: false
|
||||||
|
title: ''
|
||||||
|
type: iteration-start
|
||||||
|
draggable: false
|
||||||
|
height: 48
|
||||||
|
id: iteration_nodestart
|
||||||
|
parentId: iteration_node
|
||||||
|
position:
|
||||||
|
x: 24
|
||||||
|
y: 68
|
||||||
|
positionAbsolute:
|
||||||
|
x: 708
|
||||||
|
y: 350
|
||||||
|
selectable: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom-iteration-start
|
||||||
|
width: 44
|
||||||
|
zIndex: 1002
|
||||||
|
- data:
|
||||||
|
code: "\ndef main(arg1: int) -> dict:\n return {\n \"result\": [arg1,\
|
||||||
|
\ arg1 * 2],\n }\n"
|
||||||
|
code_language: python3
|
||||||
|
desc: ''
|
||||||
|
isInIteration: true
|
||||||
|
isInLoop: false
|
||||||
|
iteration_id: iteration_node
|
||||||
|
outputs:
|
||||||
|
result:
|
||||||
|
children: null
|
||||||
|
type: array[number]
|
||||||
|
selected: false
|
||||||
|
title: Generate Pair
|
||||||
|
type: code
|
||||||
|
variables:
|
||||||
|
- value_selector:
|
||||||
|
- iteration_node
|
||||||
|
- item
|
||||||
|
value_type: number
|
||||||
|
variable: arg1
|
||||||
|
height: 54
|
||||||
|
id: code_inner_node
|
||||||
|
parentId: iteration_node
|
||||||
|
position:
|
||||||
|
x: 128
|
||||||
|
y: 68
|
||||||
|
positionAbsolute:
|
||||||
|
x: 812
|
||||||
|
y: 350
|
||||||
|
selected: false
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 244
|
||||||
|
zIndex: 1002
|
||||||
|
- data:
|
||||||
|
desc: ''
|
||||||
|
outputs:
|
||||||
|
- value_selector:
|
||||||
|
- iteration_node
|
||||||
|
- output
|
||||||
|
value_type: array[number]
|
||||||
|
variable: output
|
||||||
|
selected: false
|
||||||
|
title: End
|
||||||
|
type: end
|
||||||
|
height: 90
|
||||||
|
id: end_node
|
||||||
|
position:
|
||||||
|
x: 1132
|
||||||
|
y: 282
|
||||||
|
positionAbsolute:
|
||||||
|
x: 1132
|
||||||
|
y: 282
|
||||||
|
selected: true
|
||||||
|
sourcePosition: right
|
||||||
|
targetPosition: left
|
||||||
|
type: custom
|
||||||
|
width: 244
|
||||||
|
viewport:
|
||||||
|
x: -476
|
||||||
|
y: 3
|
||||||
|
zoom: 1
|
||||||
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
"""
|
||||||
|
Test cases for the Iteration node's flatten_output functionality.
|
||||||
|
|
||||||
|
This module tests the iteration node's ability to:
|
||||||
|
1. Flatten array outputs when flatten_output=True (default)
|
||||||
|
2. Preserve nested array structure when flatten_output=False
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .test_table_runner import TableTestRunner, WorkflowTestCase
|
||||||
|
|
||||||
|
|
||||||
|
def test_iteration_with_flatten_output_enabled():
|
||||||
|
"""
|
||||||
|
Test iteration node with flatten_output=True (default behavior).
|
||||||
|
|
||||||
|
The fixture implements an iteration that:
|
||||||
|
1. Iterates over [1, 2, 3]
|
||||||
|
2. For each item, outputs [item, item*2]
|
||||||
|
3. With flatten_output=True, should output [1, 2, 2, 4, 3, 6]
|
||||||
|
"""
|
||||||
|
runner = TableTestRunner()
|
||||||
|
|
||||||
|
test_case = WorkflowTestCase(
|
||||||
|
fixture_path="iteration_flatten_output_enabled_workflow",
|
||||||
|
inputs={},
|
||||||
|
expected_outputs={"output": [1, 2, 2, 4, 3, 6]},
|
||||||
|
description="Iteration with flatten_output=True flattens nested arrays",
|
||||||
|
use_auto_mock=False, # Run code nodes directly
|
||||||
|
)
|
||||||
|
|
||||||
|
result = runner.run_test_case(test_case)
|
||||||
|
|
||||||
|
assert result.success, f"Test failed: {result.error}"
|
||||||
|
assert result.actual_outputs is not None, "Should have outputs"
|
||||||
|
assert result.actual_outputs == {"output": [1, 2, 2, 4, 3, 6]}, (
|
||||||
|
f"Expected flattened output [1, 2, 2, 4, 3, 6], got {result.actual_outputs}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_iteration_with_flatten_output_disabled():
|
||||||
|
"""
|
||||||
|
Test iteration node with flatten_output=False.
|
||||||
|
|
||||||
|
The fixture implements an iteration that:
|
||||||
|
1. Iterates over [1, 2, 3]
|
||||||
|
2. For each item, outputs [item, item*2]
|
||||||
|
3. With flatten_output=False, should output [[1, 2], [2, 4], [3, 6]]
|
||||||
|
"""
|
||||||
|
runner = TableTestRunner()
|
||||||
|
|
||||||
|
test_case = WorkflowTestCase(
|
||||||
|
fixture_path="iteration_flatten_output_disabled_workflow",
|
||||||
|
inputs={},
|
||||||
|
expected_outputs={"output": [[1, 2], [2, 4], [3, 6]]},
|
||||||
|
description="Iteration with flatten_output=False preserves nested structure",
|
||||||
|
use_auto_mock=False, # Run code nodes directly
|
||||||
|
)
|
||||||
|
|
||||||
|
result = runner.run_test_case(test_case)
|
||||||
|
|
||||||
|
assert result.success, f"Test failed: {result.error}"
|
||||||
|
assert result.actual_outputs is not None, "Should have outputs"
|
||||||
|
assert result.actual_outputs == {"output": [[1, 2], [2, 4], [3, 6]]}, (
|
||||||
|
f"Expected nested output [[1, 2], [2, 4], [3, 6]], got {result.actual_outputs}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_iteration_flatten_output_comparison():
|
||||||
|
"""
|
||||||
|
Run both flatten_output configurations in parallel to verify the difference.
|
||||||
|
"""
|
||||||
|
runner = TableTestRunner()
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
WorkflowTestCase(
|
||||||
|
fixture_path="iteration_flatten_output_enabled_workflow",
|
||||||
|
inputs={},
|
||||||
|
expected_outputs={"output": [1, 2, 2, 4, 3, 6]},
|
||||||
|
description="flatten_output=True: Flattened output",
|
||||||
|
use_auto_mock=False, # Run code nodes directly
|
||||||
|
),
|
||||||
|
WorkflowTestCase(
|
||||||
|
fixture_path="iteration_flatten_output_disabled_workflow",
|
||||||
|
inputs={},
|
||||||
|
expected_outputs={"output": [[1, 2], [2, 4], [3, 6]]},
|
||||||
|
description="flatten_output=False: Nested output",
|
||||||
|
use_auto_mock=False, # Run code nodes directly
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
suite_result = runner.run_table_tests(test_cases, parallel=True)
|
||||||
|
|
||||||
|
# Assert all tests passed
|
||||||
|
assert suite_result.passed_tests == 2, f"Expected 2 passed tests, got {suite_result.passed_tests}"
|
||||||
|
assert suite_result.failed_tests == 0, f"Expected 0 failed tests, got {suite_result.failed_tests}"
|
||||||
|
assert suite_result.success_rate == 100.0, f"Expected 100% success rate, got {suite_result.success_rate}"
|
||||||
|
|
@ -22,6 +22,7 @@ const nodeDefault: NodeDefault<IterationNodeType> = {
|
||||||
is_parallel: false,
|
is_parallel: false,
|
||||||
parallel_nums: 10,
|
parallel_nums: 10,
|
||||||
error_handle_mode: ErrorHandleMode.Terminated,
|
error_handle_mode: ErrorHandleMode.Terminated,
|
||||||
|
flatten_output: true,
|
||||||
},
|
},
|
||||||
checkValid(payload: IterationNodeType, t: any) {
|
checkValid(payload: IterationNodeType, t: any) {
|
||||||
let errorMessages = ''
|
let errorMessages = ''
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||||
changeParallel,
|
changeParallel,
|
||||||
changeErrorResponseMode,
|
changeErrorResponseMode,
|
||||||
changeParallelNums,
|
changeParallelNums,
|
||||||
|
changeFlattenOutput,
|
||||||
} = useConfig(id, data)
|
} = useConfig(id, data)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -117,6 +118,18 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||||
<Select items={responseMethod} defaultValue={inputs.error_handle_mode} onSelect={changeErrorResponseMode} allowSearch={false} />
|
<Select items={responseMethod} defaultValue={inputs.error_handle_mode} onSelect={changeErrorResponseMode} allowSearch={false} />
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Split />
|
||||||
|
|
||||||
|
<div className='px-4 py-2'>
|
||||||
|
<Field
|
||||||
|
title={t(`${i18nPrefix}.flattenOutput`)}
|
||||||
|
tooltip={<div className='w-[230px]'>{t(`${i18nPrefix}.flattenOutputDesc`)}</div>}
|
||||||
|
inline
|
||||||
|
>
|
||||||
|
<Switch defaultValue={inputs.flatten_output} onChange={changeFlattenOutput} />
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,6 @@ export type IterationNodeType = CommonNodeType & {
|
||||||
is_parallel: boolean // open the parallel mode or not
|
is_parallel: boolean // open the parallel mode or not
|
||||||
parallel_nums: number // the numbers of parallel
|
parallel_nums: number // the numbers of parallel
|
||||||
error_handle_mode: ErrorHandleMode // how to handle error in the iteration
|
error_handle_mode: ErrorHandleMode // how to handle error in the iteration
|
||||||
|
flatten_output: boolean // whether to flatten the output array if all elements are lists
|
||||||
_isShowTips: boolean // when answer node in parallel mode iteration show tips
|
_isShowTips: boolean // when answer node in parallel mode iteration show tips
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,14 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||||
})
|
})
|
||||||
setInputs(newInputs)
|
setInputs(newInputs)
|
||||||
}, [inputs, setInputs])
|
}, [inputs, setInputs])
|
||||||
|
|
||||||
|
const changeFlattenOutput = useCallback((value: boolean) => {
|
||||||
|
const newInputs = produce(inputs, (draft) => {
|
||||||
|
draft.flatten_output = value
|
||||||
|
})
|
||||||
|
setInputs(newInputs)
|
||||||
|
}, [inputs, setInputs])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
readOnly,
|
readOnly,
|
||||||
inputs,
|
inputs,
|
||||||
|
|
@ -109,6 +117,7 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||||
changeParallel,
|
changeParallel,
|
||||||
changeErrorResponseMode,
|
changeErrorResponseMode,
|
||||||
changeParallelNums,
|
changeParallelNums,
|
||||||
|
changeFlattenOutput,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -788,6 +788,8 @@ const translation = {
|
||||||
removeAbnormalOutput: 'Remove Abnormal Output',
|
removeAbnormalOutput: 'Remove Abnormal Output',
|
||||||
},
|
},
|
||||||
answerNodeWarningDesc: 'Parallel mode warning: Answer nodes, conversation variable assignments, and persistent read/write operations within iterations may cause exceptions.',
|
answerNodeWarningDesc: 'Parallel mode warning: Answer nodes, conversation variable assignments, and persistent read/write operations within iterations may cause exceptions.',
|
||||||
|
flattenOutput: 'Flatten Output',
|
||||||
|
flattenOutputDesc: 'When enabled, if all iteration outputs are arrays, they will be flattened into a single array. When disabled, outputs will maintain a nested array structure.',
|
||||||
},
|
},
|
||||||
loop: {
|
loop: {
|
||||||
deleteTitle: 'Delete Loop Node?',
|
deleteTitle: 'Delete Loop Node?',
|
||||||
|
|
|
||||||
|
|
@ -788,6 +788,8 @@ const translation = {
|
||||||
removeAbnormalOutput: '移除错误输出',
|
removeAbnormalOutput: '移除错误输出',
|
||||||
},
|
},
|
||||||
answerNodeWarningDesc: '并行模式警告:在迭代中,回答节点、会话变量赋值和工具持久读/写操作可能会导致异常。',
|
answerNodeWarningDesc: '并行模式警告:在迭代中,回答节点、会话变量赋值和工具持久读/写操作可能会导致异常。',
|
||||||
|
flattenOutput: '扁平化输出',
|
||||||
|
flattenOutputDesc: '启用时,如果所有迭代输出都是数组,它们将被扁平化为单个数组。禁用时,输出将保持嵌套数组结构。',
|
||||||
},
|
},
|
||||||
loop: {
|
loop: {
|
||||||
deleteTitle: '删除循环节点?',
|
deleteTitle: '删除循环节点?',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue