mirror of https://github.com/langgenius/dify.git
feat: make iteration
This commit is contained in:
parent
fbf9984d85
commit
5fdfba6b00
|
|
@ -22,7 +22,76 @@ describe('graphToLogStruct', () => {
|
|||
],
|
||||
})
|
||||
})
|
||||
test('iteration nodes', () => {
|
||||
expect(graphToLogStruct('start -> (iteration, 1, [2, 3])')).toEqual([
|
||||
{
|
||||
id: 'start',
|
||||
node_id: 'start',
|
||||
title: 'start',
|
||||
execution_metadata: {},
|
||||
status: 'succeeded',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
node_id: '1',
|
||||
title: '1',
|
||||
execution_metadata: {},
|
||||
status: 'succeeded',
|
||||
node_type: 'iteration',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
node_id: '2',
|
||||
title: '2',
|
||||
execution_metadata: { iteration_id: '1', iteration_index: 0 },
|
||||
status: 'succeeded',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
node_id: '3',
|
||||
title: '3',
|
||||
execution_metadata: { iteration_id: '1', iteration_index: 1 },
|
||||
status: 'succeeded',
|
||||
},
|
||||
])
|
||||
})
|
||||
test('retry nodes', () => {
|
||||
console.log(graphToLogStruct('start -> (retry, 1, 3)'))
|
||||
expect(graphToLogStruct('start -> (retry, 1, 3)')).toEqual([
|
||||
{
|
||||
id: 'start',
|
||||
node_id: 'start',
|
||||
title: 'start',
|
||||
execution_metadata: {},
|
||||
status: 'succeeded',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
node_id: '1',
|
||||
title: '1',
|
||||
execution_metadata: {},
|
||||
status: 'succeeded',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
node_id: '1',
|
||||
title: '1',
|
||||
execution_metadata: {},
|
||||
status: 'retry',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
node_id: '1',
|
||||
title: '1',
|
||||
execution_metadata: {},
|
||||
status: 'retry',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
node_id: '1',
|
||||
title: '1',
|
||||
execution_metadata: {},
|
||||
status: 'retry',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,18 +2,27 @@ const STEP_SPLIT = '->'
|
|||
|
||||
const toNodeData = (step: string, info: Record<string, any> = {}): any => {
|
||||
const [nodeId, title] = step.split('@')
|
||||
const data = {
|
||||
const data: Record<string, any> = {
|
||||
id: nodeId,
|
||||
node_id: nodeId,
|
||||
title: title || nodeId,
|
||||
execution_metadata: {},
|
||||
status: 'succeeded',
|
||||
}
|
||||
// const executionMetadata = data.execution_metadata
|
||||
const { isRetry } = info
|
||||
|
||||
const executionMetadata = data.execution_metadata
|
||||
const { isRetry, isIteration, inIterationInfo } = info
|
||||
if (isRetry)
|
||||
data.status = 'retry'
|
||||
|
||||
if (isIteration)
|
||||
data.node_type = 'iteration'
|
||||
|
||||
if (inIterationInfo) {
|
||||
executionMetadata.iteration_id = inIterationInfo.iterationId
|
||||
executionMetadata.iteration_index = inIterationInfo.iterationIndex
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
|
@ -30,6 +39,21 @@ const toRetryNodeData = ({
|
|||
return res
|
||||
}
|
||||
|
||||
const toIterationNodeData = ({
|
||||
nodeId,
|
||||
children,
|
||||
}: {
|
||||
nodeId: string,
|
||||
children: number[],
|
||||
}) => {
|
||||
const res = [toNodeData(nodeId, { isIteration: true })]
|
||||
// TODO: handle inner node structure
|
||||
for (let i = 0; i < children.length; i++)
|
||||
res.push(toNodeData(`${children[i]}`, { inIterationInfo: { iterationId: nodeId, iterationIndex: i } }))
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type NodeStructure = {
|
||||
node: string;
|
||||
params: Array<string | NodeStructure>;
|
||||
|
|
@ -43,6 +67,7 @@ export function parseNodeString(input: string): NodeStructure {
|
|||
const parts: Array<string | NodeStructure> = []
|
||||
let current = ''
|
||||
let depth = 0
|
||||
let inArrayDepth = 0
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const char = input[i]
|
||||
|
|
@ -52,7 +77,14 @@ export function parseNodeString(input: string): NodeStructure {
|
|||
else if (char === ')')
|
||||
depth--
|
||||
|
||||
if (char === ',' && depth === 0) {
|
||||
if (char === '[')
|
||||
inArrayDepth++
|
||||
else if (char === ']')
|
||||
inArrayDepth--
|
||||
|
||||
const isInArray = inArrayDepth > 0
|
||||
|
||||
if (char === ',' && depth === 0 && !isInArray) {
|
||||
parts.push(current.trim())
|
||||
current = ''
|
||||
}
|
||||
|
|
@ -97,6 +129,12 @@ const toNodes = (input: string): any[] => {
|
|||
|
||||
const { node, params } = parseNodeString(step)
|
||||
switch (node) {
|
||||
case 'iteration':
|
||||
res.push(...toIterationNodeData({
|
||||
nodeId: params[0] as string,
|
||||
children: JSON.parse(params[1] as string) as number[],
|
||||
}))
|
||||
break
|
||||
case 'retry':
|
||||
res.push(...toRetryNodeData({
|
||||
nodeId: params[0] as string,
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
export const simpleIterationData = (() => {
|
||||
// start -> code(output: [1, 2, 3]) -> iteration(output: ['aaa', 'aaa', 'aaa']) -> end(output: ['aaa', 'aaa', 'aaa'])
|
||||
const startNode = {
|
||||
id: '36c9860a-39e6-4107-b750-655b07895f47',
|
||||
index: 1,
|
||||
predecessor_node_id: null,
|
||||
node_id: '1735023354069',
|
||||
node_type: 'start',
|
||||
title: 'Start',
|
||||
inputs: {
|
||||
'sys.files': [],
|
||||
'sys.user_id': '5ee03762-1d1a-46e8-ba0b-5f419a77da96',
|
||||
'sys.app_id': '8a5e87f8-6433-40f4-a67a-4be78a558dc7',
|
||||
'sys.workflow_id': 'bb5e2b89-40ac-45c9-9ccb-4f2cd926e080',
|
||||
'sys.workflow_run_id': '76adf675-a7d3-4cc1-9282-ed7ecfe4f65d',
|
||||
},
|
||||
process_data: null,
|
||||
outputs: {
|
||||
'sys.files': [],
|
||||
'sys.user_id': '5ee03762-1d1a-46e8-ba0b-5f419a77da96',
|
||||
'sys.app_id': '8a5e87f8-6433-40f4-a67a-4be78a558dc7',
|
||||
'sys.workflow_id': 'bb5e2b89-40ac-45c9-9ccb-4f2cd926e080',
|
||||
'sys.workflow_run_id': '76adf675-a7d3-4cc1-9282-ed7ecfe4f65d',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.011458,
|
||||
execution_metadata: null,
|
||||
extras: {},
|
||||
created_by_end_user: null,
|
||||
finished_at: 1735023510,
|
||||
}
|
||||
|
||||
const outputArrayNode = {
|
||||
id: 'a3105c5d-ff9e-44ea-9f4c-ab428958af20',
|
||||
index: 2,
|
||||
predecessor_node_id: '1735023354069',
|
||||
node_id: '1735023361224',
|
||||
node_type: 'code',
|
||||
title: 'Code',
|
||||
inputs: null,
|
||||
process_data: null,
|
||||
outputs: {
|
||||
result: [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
],
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.103333,
|
||||
execution_metadata: null,
|
||||
extras: {},
|
||||
finished_at: 1735023511,
|
||||
}
|
||||
|
||||
const iterationNode = {
|
||||
id: 'a823134d-9f1a-45a4-8977-db838d076316',
|
||||
index: 3,
|
||||
predecessor_node_id: '1735023361224',
|
||||
node_id: '1735023391914',
|
||||
node_type: 'iteration',
|
||||
title: 'Iteration',
|
||||
inputs: null,
|
||||
process_data: null,
|
||||
outputs: {
|
||||
output: [
|
||||
'aaa',
|
||||
'aaa',
|
||||
'aaa',
|
||||
],
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const iterations = [
|
||||
{
|
||||
id: 'a84a22d8-0f08-4006-bee2-fa7a7aef0420',
|
||||
index: 4,
|
||||
predecessor_node_id: '1735023391914start',
|
||||
node_id: '1735023409906',
|
||||
node_type: 'code',
|
||||
title: 'Code 2',
|
||||
inputs: null,
|
||||
process_data: null,
|
||||
outputs: {
|
||||
result: 'aaa',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.112688,
|
||||
execution_metadata: {
|
||||
iteration_id: '1735023391914',
|
||||
iteration_index: 0,
|
||||
},
|
||||
extras: {},
|
||||
created_at: 1735023511,
|
||||
finished_at: 1735023511,
|
||||
},
|
||||
{
|
||||
id: 'ff71d773-a916-4513-960f-d7dcc4fadd86',
|
||||
index: 5,
|
||||
predecessor_node_id: '1735023391914start',
|
||||
node_id: '1735023409906',
|
||||
node_type: 'code',
|
||||
title: 'Code 2',
|
||||
inputs: null,
|
||||
process_data: null,
|
||||
outputs: {
|
||||
result: 'aaa',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.126034,
|
||||
execution_metadata: {
|
||||
iteration_id: '1735023391914',
|
||||
iteration_index: 1,
|
||||
},
|
||||
extras: {},
|
||||
created_at: 1735023511,
|
||||
finished_at: 1735023511,
|
||||
},
|
||||
{
|
||||
id: 'd91c3ef9-0162-4013-9272-d4cc7fb1f188',
|
||||
index: 6,
|
||||
predecessor_node_id: '1735023391914start',
|
||||
node_id: '1735023409906',
|
||||
node_type: 'code',
|
||||
title: 'Code 2',
|
||||
inputs: null,
|
||||
process_data: null,
|
||||
outputs: {
|
||||
result: 'aaa',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.122716,
|
||||
execution_metadata: {
|
||||
iteration_id: '1735023391914',
|
||||
iteration_index: 2,
|
||||
},
|
||||
extras: {},
|
||||
created_at: 1735023511,
|
||||
finished_at: 1735023511,
|
||||
},
|
||||
]
|
||||
|
||||
const endNode = {
|
||||
id: 'e6ad6560-1aa3-43f3-89e3-e5287c9ea272',
|
||||
index: 7,
|
||||
predecessor_node_id: '1735023391914',
|
||||
node_id: '1735023417757',
|
||||
node_type: 'end',
|
||||
title: 'End',
|
||||
inputs: {
|
||||
output: [
|
||||
'aaa',
|
||||
'aaa',
|
||||
'aaa',
|
||||
],
|
||||
},
|
||||
process_data: null,
|
||||
outputs: {
|
||||
output: [
|
||||
'aaa',
|
||||
'aaa',
|
||||
'aaa',
|
||||
],
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.017552,
|
||||
execution_metadata: null,
|
||||
extras: {},
|
||||
finished_at: 1735023511,
|
||||
}
|
||||
|
||||
return {
|
||||
in: [startNode, outputArrayNode, iterationNode, ...iterations, endNode],
|
||||
expect: [startNode, outputArrayNode, {
|
||||
...iterationNode,
|
||||
details: [
|
||||
[iterations[0]],
|
||||
[iterations[1]],
|
||||
[iterations[2]],
|
||||
],
|
||||
}, endNode],
|
||||
}
|
||||
})()
|
||||
|
|
@ -1,11 +1,23 @@
|
|||
import format from '.'
|
||||
import { simpleIterationData } from './data'
|
||||
import graphToLogStruct from '../graph-to-log-struct'
|
||||
|
||||
describe('iteration', () => {
|
||||
const list = graphToLogStruct('start -> (iteration, 1, [2, 3])')
|
||||
const [startNode, iterationNode, ...iterations] = graphToLogStruct('start -> (iteration, 1, [2, 3])')
|
||||
const result = format(list as any, () => { })
|
||||
test('result should have no nodes in iteration node', () => {
|
||||
expect(format(simpleIterationData.in as any).find(item => !!(item as any).execution_metadata?.iteration_id)).toBeUndefined()
|
||||
expect((result as any).find((item: any) => !!item.execution_metadata?.iteration_id)).toBeUndefined()
|
||||
})
|
||||
test('iteration should put nodes in details', () => {
|
||||
expect(format(simpleIterationData.in as any)).toEqual(simpleIterationData.expect)
|
||||
expect(result as any).toEqual([
|
||||
startNode,
|
||||
{
|
||||
...iterationNode,
|
||||
details: [
|
||||
[iterations[0]],
|
||||
[iterations[1]],
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue