langgenius / dify

Dify is an open-source LLM app development platform. Dify's intuitive interface combines AI workflow, RAG pipeline, agent capabilities, model management, observability features and more, letting you quickly go from prototype to production.
https://dify.ai
Other
48.65k stars 6.97k forks source link

Cannot Link Node to start in iteration node #5939

Open WeepsDanky opened 3 months ago

WeepsDanky commented 3 months ago

Self Checks

Dify version

0.6.12

Cloud or Self Hosted

Cloud

Steps to reproduce

image

  1. Add a iteraction node and add a few llm nodes inside iteraction
  2. disconnect the first one so it would look like the situation in the image
  3. ideally you should be able to connect it back but you cannot

✔️ Expected Behavior

The node within iteraction node should be able to connect back to the start of iteraction node after disconnected.

❌ Actual Behavior

Once disconnect it cannot connect back to the start image

dosubot[bot] commented 3 months ago

To reconnect a node to the start of an iteration node after it has been disconnected in Dify version 0.6.12 (Cloud), you can use the handleNodeConnect function. Here is a relevant snippet from the provided code:

const handleNodeConnect = useCallback<OnConnect>(({
    source,
    sourceHandle,
    target,
    targetHandle,
  }) => {
    if (source === target)
      return
    if (getNodesReadOnly())
      return

    const {
      getNodes,
      setNodes,
      edges,
      setEdges,
    } = store.getState()
    const nodes = getNodes()
    const targetNode = nodes.find(node => node.id === target!)
    const sourceNode = nodes.find(node => node.id === source!)

    if (targetNode?.parentId !== sourceNode?.parentId)
      return

    if (targetNode?.data.isIterationStart)
      return

    if (sourceNode?.type === CUSTOM_NOTE_NODE || targetNode?.type === CUSTOM_NOTE_NODE)
      return

    const needDeleteEdges = edges.filter((edge) => {
      if (
        (edge.source === source && edge.sourceHandle === sourceHandle)
        || (edge.target === target && targetHandle === targetHandle && targetNode?.data.type !== BlockEnum.VariableAssigner && targetNode?.data.type !== BlockEnum.VariableAggregator)
      )
        return true
    })

    setEdges(edges.filter(edge => !needDeleteEdges.includes(edge)))
    setEdges([...edges, {
      id: `${source}-${sourceHandle}-${target}-${targetHandle}`,
      source,
      sourceHandle,
      target,
      targetHandle,
      type: 'custom',
    }])
  }, [store, getNodesReadOnly])

This function handles the connection between nodes, ensuring that the nodes are not of type CUSTOM_NOTE_NODE and that the target node is not the start of an iteration. You can call this function with the appropriate parameters to reconnect your node to the start of the iteration node [1].

Additionally, the WorkflowEngineManager class in the Dify platform provides methods to handle iteration nodes and manage workflow states. Specifically, the _workflow_iteration_started and _workflow_iteration_next methods are used to manage the state and transitions of iteration nodes. Here is a relevant snippet:

# handle iteration nodes
if isinstance(next_node, BaseIterationNode):
    current_iteration_node = next_node
    workflow_run_state.current_iteration_state = next_node.run(
        variable_pool=workflow_run_state.variable_pool
    )
    self._workflow_iteration_started(
        graph=graph,
        current_iteration_node=current_iteration_node,
        workflow_run_state=workflow_run_state,
        predecessor_node_id=predecessor_node.node_id if predecessor_node else None,
        callbacks=callbacks
    )
    predecessor_node = next_node
    # move to start node of iteration
    next_node_id = next_node.get_next_iteration(
        variable_pool=workflow_run_state.variable_pool,
        state=workflow_run_state.current_iteration_state
    )
    self._workflow_iteration_next(
        graph=graph,
        current_iteration_node=current_iteration_node,
        workflow_run_state=workflow_run_state,
        callbacks=callbacks
    )
    if isinstance(next_node_id, NodeRunResult):
        # iteration has ended
        current_iteration_node.set_output(
            variable_pool=workflow_run_state.variable_pool,
            state=workflow_run_state.current_iteration_state
        )
        self._workflow_iteration_completed(
            current_iteration_node=current_iteration_node,
            workflow_run_state=workflow_run_state,
            callbacks=callbacks
        )
        current_iteration_node = None
        workflow_run_state.current_iteration_state = None
        continue
    else:
        next_node = self._get_node(workflow_run_state, graph, next_node_id, callbacks)

This code ensures that the workflow correctly handles the start and end of iteration nodes, maintaining the state and transitioning to the next node as needed [2].

To continue talking to Dosu, mention @dosu.

JokerQyou commented 1 month ago

Why was this closed as not planned? This is still an issue in the latest version, and clearly a design problem. Once the first node in an Iteration block is deleted, you cannot connect any remaining nodes back to the start of Iteration. This basically makes Iteration un-editable. You should re-open this issue.