def search(config: Config, index_name: str, text: Optional[Union[str, dict, CustomVectorQuery]],
           result_count: int = 3, offset: int = 0, rerank_depth: Optional[int] = None,
           highlights: bool = True, ef_search: Optional[int] = None,
           approximate: Optional[bool] = None, approximate_threshold: Optional[float] = None,
           search_method: Union[str, SearchMethod, None] = SearchMethod.TENSOR,
           searchable_attributes: Iterable[str] = None, verbose: int = 0,
           reranker: Union[str, Dict] = None, filter: Optional[str] = None,
           attributes_to_retrieve: Optional[List[str]] = None,
           device: str = None, boost: Optional[Dict] = None,
           media_download_headers: Optional[Dict] = None,
           context: Optional[SearchContext] = None,
           score_modifiers: Optional[ScoreModifierLists] = None,
           model_auth: Optional[ModelAuth] = None,
           processing_start: float = None,
           text_query_prefix: Optional[str] = None,
           hybrid_parameters: Optional[HybridParameters] = None,
           facets: Optional[FacetsParameters] = None,
           track_total_hits: Optional[bool] = None,
           ) -> Dict:
    """The root search method. Calls the specific search method

    Validation should go here. Validations include:
        - all args and their types
        - result_count (negatives etc)
        - text

    This deals with index caching

    Args:
        config:
        index_name:
        text:
        result_count:
        offset:
        rerank_depth:
        search_method:
        searchable_attributes:
        verbose:
        device: May be none, we calculate default device here
        num_highlights: number of highlights to return for each doc
        boost: boosters to re-weight the scores of individual fields
        media_download_headers: headers to use when downloading media
        context: a dictionary to allow custom vectors in search, for tensor search only
        score_modifiers: a dictionary to modify the score based on field values, for tensor search only
        model_auth: Authorisation details for downloading a model (if required)
        text_query_prefix: The prefix to be used for chunking text fields or search queries.
        hybrid_parameters: Parameters for hybrid search
        facets: Parameters for facets
    Returns:

    """

    # Validation for: result_count (limit) & offset
    # Validate neither is negative
    if result_count <= 0 or (not isinstance(result_count, int)):
        raise errors.IllegalRequestedDocCount(
            f"result_count must be an integer greater than 0! Received {result_count}"
        )

    if offset < 0:
        raise api_exceptions.IllegalRequestedDocCount("search result offset cannot be less than 0!")

        # validate query
    validation.validate_query(q=text, search_method=search_method)

    # Validate max limits
    max_docs_limit = utils.read_env_vars_and_defaults(EnvVars.MARQO_MAX_RETRIEVABLE_DOCS)
    max_search_limit = utils.read_env_vars_and_defaults(EnvVars.MARQO_MAX_SEARCH_LIMIT)
    max_search_offset = utils.read_env_vars_and_defaults(EnvVars.MARQO_MAX_SEARCH_OFFSET)

    check_upper = True if max_docs_limit is None else result_count + offset <= int(max_docs_limit)
    check_limit = True if max_search_limit is None else result_count <= int(max_search_limit)
    check_offset = True if max_search_offset is None else offset <= int(max_search_offset)
    if not check_upper:
        upper_bound_explanation = ("The search result limit + offset must be less than or equal to the "
                                   f"MARQO_MAX_RETRIEVABLE_DOCS limit of [{max_docs_limit}]. ")

        raise api_exceptions.IllegalRequestedDocCount(
            f"{upper_bound_explanation} Marqo received search result limit of `{result_count}` "
            f"and offset of `{offset}`.")
    if not check_limit:
        raise api_exceptions.IllegalRequestedDocCount(
            f"The search result limit must be less than or equal to the MARQO_MAX_SEARCH_LIMIT limit of "
            f"[{max_search_limit}]. Marqo received search result limit of `{result_count}`.")
    if not check_offset:
        raise api_exceptions.IllegalRequestedDocCount(
            f"The search result offset must be less than or equal to the MARQO_MAX_SEARCH_OFFSET limit of "
            f"[{max_search_offset}]. Marqo received search result offset of `{offset}`.")

    if processing_start is None:
        t0 = timer()
    else:
        t0 = processing_start

    validation.validate_context(context=context, query=text, search_method=search_method)
    validation.validate_boost(boost=boost, search_method=search_method)
    validation.validate_searchable_attributes(searchable_attributes=searchable_attributes, search_method=search_method)
    if searchable_attributes is not None:
        [validation.validate_field_name(attribute) for attribute in searchable_attributes]
    if attributes_to_retrieve is not None:
        [validation.validate_field_name(attribute) for attribute in attributes_to_retrieve]
    if verbose:
        print(f"determined_search_method: {search_method}, text query: {text}")

    selected_device = device

    # Fetch marqo index to pass to search method
    marqo_index = index_meta_cache.get_index(index_management=config.index_management, index_name=index_name)
    marqo_index_version = marqo_index.parsed_marqo_version()
    if rerank_depth is not None \
            and marqo_index_version < constants.MARQO_RERANK_DEPTH_MINIMUM_VERSION:
        raise core_exceptions.UnsupportedFeatureError(
            f"The 'rerankDepth' search parameter is only supported for indexes created with Marqo version "
            f"{str(constants.MARQO_RERANK_DEPTH_MINIMUM_VERSION)} or later. "
            f"This index was created with Marqo {marqo_index_version}."
        )

    if search_method.upper() in {SearchMethod.TENSOR, SearchMethod.HYBRID}:
        # Default approximate and efSearch -- we can't set these at API-level since they're not a valid args
        # for lexical search
        if approximate is None:
            approximate = True

        if search_method.upper() == SearchMethod.TENSOR:
            search_result = _vector_text_search(
                config=config, marqo_index=marqo_index, query=text, result_count=result_count, offset=offset,
                ef_search=ef_search, approximate=approximate, approximate_threshold=approximate_threshold,
                searchable_attributes=searchable_attributes,
                filter_string=filter, device=selected_device, attributes_to_retrieve=attributes_to_retrieve,
                boost=boost,
                media_download_headers=media_download_headers, context=context, score_modifiers=score_modifiers,
                model_auth=model_auth, highlights=highlights, text_query_prefix=text_query_prefix,
                rerank_depth=rerank_depth
            )
        elif search_method.upper() == SearchMethod.HYBRID:
            # TODO: Deal with circular import when all modules are refactored out.
            from marqo.core.search.hybrid_search import HybridSearch
            search_result = HybridSearch().search(
                config=config, marqo_index=marqo_index, query=text, result_count=result_count, offset=offset,
                rerank_depth=rerank_depth,
                ef_search=ef_search, approximate=approximate, approximate_threshold=approximate_threshold,
                searchable_attributes=searchable_attributes,
                filter_string=filter, device=selected_device, attributes_to_retrieve=attributes_to_retrieve,
                boost=boost,
                media_download_headers=media_download_headers, context=context, score_modifiers=score_modifiers,
                model_auth=model_auth, highlights=highlights, text_query_prefix=text_query_prefix,
                hybrid_parameters=hybrid_parameters, facets=facets, track_total_hits=track_total_hits
            )

    elif search_method.upper() == SearchMethod.LEXICAL:
        if ef_search is not None:
            raise errors.InvalidArgError(
                f"efSearch is not a valid argument for lexical search")
        if approximate is not None:
            raise errors.InvalidArgError(
                f"approximate is not a valid argument for lexical search")

        search_result = _lexical_search(
            config=config, marqo_index=marqo_index, text=text, result_count=result_count, offset=offset,
            searchable_attributes=searchable_attributes, verbose=verbose,
            filter_string=filter, attributes_to_retrieve=attributes_to_retrieve, highlights=highlights,
            score_modifiers=score_modifiers
        )
    else:
        raise api_exceptions.InvalidArgError(f"Search called with unknown search method: {search_method}")

    if reranker is not None:
        raise api_exceptions.InvalidArgError(f"Reranker is no longer supported in Marqo version 2.17 and later")

    if isinstance(text, CustomVectorQuery):
        search_result["query"] = text.dict()  # Make object JSON serializable
    else:
        search_result["query"] = text

    search_result["limit"] = result_count
    search_result["offset"] = offset

    time_taken = timer() - t0
    search_result["processingTimeMs"] = round(time_taken * 1000)
    logger.debug(f"search ({search_method.lower()}) completed with total processing time: {(time_taken):.3f}s.")

    return search_result
The root search method. Calls the specific search method

    Validation should go here. Validations include:
        - all args and their types
        - result_count (negatives etc)
        - text

    This deals with index caching

    Args:
        config:
        index_name:
        text:
        result_count:
        offset:
        rerank_depth:
        search_method:
        searchable_attributes:
        verbose:
        device: May be none, we calculate default device here
        num_highlights: number of highlights to return for each doc
        boost: boosters to re-weight the scores of individual fields
        media_download_headers: headers to use when downloading media
        context: a dictionary to allow custom vectors in search, for tensor search only
        score_modifiers: a dictionary to modify the score based on field values, for tensor search only
        model_auth: Authorisation details for downloading a model (if required)
        text_query_prefix: The prefix to be used for chunking text fields or search queries.
        hybrid_parameters: Parameters for hybrid search
        facets: Parameters for facets
    Returns:

    
src/marqo/tensor_search/tensor_search.py