def _vector_text_search(
        config: Config, marqo_index: MarqoIndex,
        query: Optional[Union[str, dict, CustomVectorQuery]], result_count: int = 5,
        offset: int = 0,
        ef_search: Optional[int] = None, approximate: bool = True, approximate_threshold: Optional[float] = None,
        searchable_attributes: Iterable[str] = None, filter_string: str = None, device: str = None,
        attributes_to_retrieve: Optional[List[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,
        highlights: bool = False, text_query_prefix: Optional[str] = None, rerank_depth: Optional[int] = None
) -> Dict:
    """

    Args:
        config:
        marqo_index: index object fetched by calling function
        query: either a string query (which can be a URL or natural language text), a dict of
            <query string>:<weight float> pairs, or None with a context
        result_count:
        offset:
        searchable_attributes: Iterable of field names to search. If left as None, then all will
            be searched
        verbose: if 0 - nothing is printed. if 1 - data is printed without vectors, if 2 - full
            objects are printed out
        attributes_to_retrieve: if set, only returns these fields
        media_download_headers: headers for downloading media
        context: a dictionary to allow custom vectors in search
        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)
        highlights: if True, highlights will be returned
        text_query_prefix: prefix to add to text queries
        rerank_depth: the number of hits per shard during retrieval
    Returns:

    Note:
        - uses multisearch, which returns k results in each attribute. Not that much of a concern unless you have a
        ridiculous number of attributes
        - Should not be directly called by client - the search() method should
        be called. The search() method adds syncing
        - device should ALWAYS be set

    Output format:
        [
            {
                _id: doc_id
                doc: {# original document},
                highlights:[{}],
            },
        ]
    Future work:
        - max result count should be in a config somewhere
        - searching a non existent index should return a HTTP-type error
    """
    # # SEARCH TIMER-LOGGER (pre-processing)
    RequestMetricsStore.for_request().start("search.vector.processing_before_vespa")

    index_name = marqo_index.name

    # Determine the text query prefix
    text_query_prefix = marqo_index.model.get_text_query_prefix(text_query_prefix)

    if isinstance(query, CustomVectorQuery):
        if context is None:
            context = SearchContext(
                tensor=[SearchContextTensor(vector=query.customVector.vector, weight=1)]
            )
        else:
            context.tensor.append(SearchContextTensor(vector=query.customVector.vector, weight=1))
        query = None

    queries = [BulkSearchQueryEntity(
        q=query, searchableAttributes=searchable_attributes, searchMethod=SearchMethod.TENSOR, limit=result_count,
        offset=offset, showHighlights=False, filter=filter_string, attributesToRetrieve=attributes_to_retrieve,
        boost=boost, mediaDownloadHeaders=media_download_headers, context=context, scoreModifiers=score_modifiers,
        index=marqo_index, modelAuth=model_auth, text_query_prefix=text_query_prefix, rerankDepth=rerank_depth
    )]

    with RequestMetricsStore.for_request().time(f"search.vector_inference_full_pipeline"):
        qidx_to_vectors: Dict[Qidx, List[float]] = run_vectorise_pipeline(config, queries, device)
    vectorised_text = list(qidx_to_vectors.values())[0]

    marqo_query = MarqoTensorQuery(
        index_name=index_name,
        vector_query=vectorised_text,
        filter=filter_string,
        limit=result_count,
        ef_search=ef_search,
        approximate=approximate,
        approximate_threshold=approximate_threshold,
        offset=offset,
        searchable_attributes=searchable_attributes,
        attributes_to_retrieve=attributes_to_retrieve,
        score_modifiers=score_modifiers.to_marqo_score_modifiers() if score_modifiers is not None else None,
        rerank_depth_tensor=rerank_depth
    )

    vespa_index = vespa_index_factory(marqo_index)
    vespa_query = vespa_index.to_vespa_query(marqo_query)

    total_preprocess_time = RequestMetricsStore.for_request().stop("search.vector.processing_before_vespa")
    logger.debug(
        f"search (tensor) pre-processing: took {(total_preprocess_time):.3f}ms to vectorize and process query.")

    # SEARCH TIMER-LOGGER (roundtrip)
    with RequestMetricsStore.for_request().time("search.vector.vespa",
                                                lambda t: logger.debug(f"Vespa search: took {t:.3f}ms")
                                                ):
        try:
            responses = config.vespa_client.query(**vespa_query)
        except VespaStatusError as e:
            # The index will not have the embedding_similarity rank profile if there are no tensor fields
            if f"No profile named '{RANK_PROFILE_EMBEDDING_SIMILARITY}'" in e.message:
                raise core_exceptions.InvalidArgumentError(
                    f"Index {index_name} has no tensor fields, thus tensor search cannot be performed. "
                    f"Please create an index with a tensor field, or try a different search method."
                )
            raise e

    if not approximate and (responses.root.coverage.coverage < 100 or responses.root.coverage.degraded is not None):
        raise errors.InternalError(
            f'Graceful degradation detected for non-approximate search. '
            f'Coverage is not 100%: {responses.root.coverage}'
            f'Degraded: {str(responses.root.coverage.degraded)}'
        )

    # SEARCH TIMER-LOGGER (post-processing)
    RequestMetricsStore.for_request().start("search.vector.postprocess")
    gathered_docs = gather_documents_from_response(responses, marqo_index, highlights, attributes_to_retrieve)

    if boost is not None:
        raise api_exceptions.MarqoWebError('Boosting is not currently supported with Vespa')

    total_postprocess_time = RequestMetricsStore.for_request().stop("search.vector.postprocess")
    logger.debug(
        f"search (tensor) post-processing: took {(total_postprocess_time):.3f}ms to sort and format "
        f"{len(gathered_docs)} results from Vespa."
    )

    return gathered_docs


    Args:
        config:
        marqo_index: index object fetched by calling function
        query: either a string query (which can be a URL or natural language text), a dict of
            <query string>:<weight float> pairs, or None with a context
        result_count:
        offset:
        searchable_attributes: Iterable of field names to search. If left as None, then all will
            be searched
        verbose: if 0 - nothing is printed. if 1 - data is printed without vectors, if 2 - full
            objects are printed out
        attributes_to_retrieve: if set, only returns these fields
        media_download_headers: headers for downloading media
        context: a dictionary to allow custom vectors in search
        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)
        highlights: if True, highlights will be returned
        text_query_prefix: prefix to add to text queries
        rerank_depth: the number of hits per shard during retrieval
    Returns:

    Note:
        - uses multisearch, which returns k results in each attribute. Not that much of a concern unless you have a
        ridiculous number of attributes
        - Should not be directly called by client - the search() method should
        be called. The search() method adds syncing
        - device should ALWAYS be set

    Output format:
        [
            {
                _id: doc_id
                doc: {# original document},
                highlights:[{}],
            },
        ]
    Future work:
        - max result count should be in a config somewhere
        - searching a non existent index should return a HTTP-type error
    
src/marqo/tensor_search/tensor_search.py