def apply_at_node(modifier, primitive_set, terminal_set, tree, node_id):
    """
    Recurse through the tree until the node is found
    Then apply an operation to the node

    Args:
        modifier: callable function that modifies a Node
        primitive_set: dictionary where (key, value) is (output_type, [{"name", "input_types", "group"}, ...])
        terminal_set:  dictionary where (key, value) is (output_type, [{"name", "generator", "static"}, ...])
        tree: Node containing full tree
        node_id: string id directing which nodes to traverse

    Returns:
        Node containing full tree
    """
    # We have reached the correct node if the string is empty
    if node_id == "":
        # Apply the modification
        tree = modifier(primitive_set, terminal_set, tree)

        # Return the modified tree node
        return tree

    # This gets casted to an integer so it can be used as an index
    node_index = int(node_id[0])
    # The next node is the correct node if the length of the node_id is 1
    if len(node_id) == 1:
        next_id = ""
    else:
        next_id = node_id[1:]

    # Set the correct child to be the result of the recursion
    tree.args[node_index] = apply_at_node(modifier, primitive_set, terminal_set, tree.args[node_index], next_id)

    # Update the tree_ids and input_ids of the tree
    tree.update_tree_ids()
    tree.update_input_ids()

    return tree