code: def _merge_equivalent_nodes(
      graph: "networkx.MultiDiGraph", max_history: int
  ) -> None:
      """Searches for equivalent nodes in the graph and merges them."""
      changed = True
      # every node merge changes the graph and can trigger previously
      # impossible node merges - we need to repeat until
      # the graph doesn't change anymore
      while changed:
          changed = False
          remaining_node_ids = [n for n in graph.nodes() if n > 0]
          for idx, i in enumerate(remaining_node_ids):
              if graph.has_node(i):
                  # assumes node equivalence is cumulative
                  for j in remaining_node_ids[idx + 1 :]:
                      if graph.has_node(j) and _nodes_are_equivalent(
                          graph, i, j, max_history
                      ):
                          # make sure we keep special styles
                          _transfer_style(
                              graph.nodes(data=True)[j],
                              graph.nodes(data=True)[i],
                          )
  
                          changed = True
                          # moves all outgoing edges to the other node
                          j_outgoing_edges = list(
                              graph.out_edges(j, keys=True, data=True)
                          )
                          for _, succ_node, k, d in j_outgoing_edges:
                              _add_edge(
                                  graph,
                                  i,
                                  succ_node,
                                  k,
                                  d.get("label"),
                                  **{"class": d.get("class", "")},
                              )
                              graph.remove_edge(j, succ_node)
                          # moves all incoming edges to the other node
                          j_incoming_edges = list(
                              graph.in_edges(j, keys=True, data=True)
                          )
                          for prev_node, _, k, d in j_incoming_edges:
                              _add_edge(
                                  graph,
                                  prev_node,
                                  i,
                                  k,
                                  d.get("label"),
                                  **{"class": d.get("class", "")},
                              )
                              graph.remove_edge(prev_node, j)
                          graph.remove_node(j)
  
imports: from collections import defaultdict, deque
  import random
  from typing import Any, Text, List, Deque, Dict, Optional, Set, TYPE_CHECKING, Union, cast
  import rasa.shared.utils.io
  from rasa.shared.constants import INTENT_MESSAGE_PREFIX
  from rasa.shared.core.constants import ACTION_LISTEN_NAME
  from rasa.shared.core.domain import Domain
  from rasa.shared.core.events import UserUttered, ActionExecuted, Event
  from rasa.shared.core.generator import TrainingDataGenerator
  from rasa.shared.core.training_data.structures import StoryGraph, StoryStep
  from rasa.shared.nlu.constants import ENTITY_ATTRIBUTE_VALUE, INTENT, TEXT, ENTITY_ATTRIBUTE_TYPE, INTENT_NAME_KEY
label_desc: Use image node_id 8 for calling a global function with example usage: _merge_equivalent_nodes(graph, max_history) without return types