def add_documents(self) -> MarqoAddDocumentsResponse:
        """
        Template method for adding documents to a Marqo index. This method define a generic workflow to add documents
        in batches:
        1. Traverse the docs list in reserved order to skip duplicate documents
        2. for each document, do validation first, and collect it to a dictionary
        3. tensor field information will be collected in `tensor_fields_container`
        4. Populate tensors from existing docs if `use_existing_tensors` is specified in the add_docs_params
        5. Vectorise the remaining tensor fields (including downloading, preprocessing, chunking)
        6. Convert the marqo docs to Vespa docs
        7. Persist all Vespa docs to vespa in batches
        8. Collect the response and return

        Index-type-agnostic logic are implemented in this class, and type-specific logic are extracted as abstract
        methods and implemented in add_docs_handler for individual types.
        """
        with RequestMetricsStore.for_request().time("add_documents.processing_before_vespa"):
            for loc, doc in enumerate(reversed(self.add_docs_params.docs)):
                original_id = None
                try:
                    self._validate_doc(doc)
                    # If _id is not provide, generate a ramdom one
                    original_id = doc.get(MARQO_DOC_ID)
                    marqo_doc = {MARQO_DOC_ID: original_id or str(uuid.uuid4())}

                    for field_name, field_content in doc.items():
                        if field_name == MARQO_DOC_ID:
                            continue  # we don't handle _id field
                        self._handle_field(marqo_doc, field_name, field_content)

                    self._handle_multi_modal_fields(marqo_doc)

                    self.add_docs_response_collector.collect_marqo_doc(loc, marqo_doc, original_id)
                except AddDocumentsError as err:
                    self.add_docs_response_collector.collect_error_response(original_id, err, loc)

            # retrieve existing docs for existing tensor
            if self.add_docs_params.use_existing_tensors:
                with RequestMetricsStore.for_request().time("add_documents.vespa._get_batch"):
                    result = self.vespa_client.get_batch(ids=list(self.add_docs_response_collector.valid_original_ids()),
                                                         schema=self.marqo_index.schema_name)
                existing_vespa_docs = [r.document for r in result.responses if r.status == 200]
                self._populate_existing_tensors(existing_vespa_docs)

            # vectorise tensor fields
            with RequestMetricsStore.for_request().time("add_documents.inference.all"):
                self._vectorise_tensor_fields()

        with RequestMetricsStore.for_request().time("add_documents.vespa.to_vespa_docs"):
            vespa_docs = self._convert_to_vespa_docs()

        self._pre_persist_to_vespa()

        # persist to vespa if there are still valid docs
        with RequestMetricsStore.for_request().time("add_documents.vespa._bulk"):
            response = self.vespa_client.feed_batch(vespa_docs, self.marqo_index.schema_name)

        with RequestMetricsStore.for_request().time("add_documents.postprocess"):
            self._handle_vespa_response(response)

        return self.add_docs_response_collector.to_add_doc_responses(self.marqo_index.name)

        Template method for adding documents to a Marqo index. This method define a generic workflow to add documents
        in batches:
        1. Traverse the docs list in reserved order to skip duplicate documents
        2. for each document, do validation first, and collect it to a dictionary
        3. tensor field information will be collected in `tensor_fields_container`
        4. Populate tensors from existing docs if `use_existing_tensors` is specified in the add_docs_params
        5. Vectorise the remaining tensor fields (including downloading, preprocessing, chunking)
        6. Convert the marqo docs to Vespa docs
        7. Persist all Vespa docs to vespa in batches
        8. Collect the response and return

        Index-type-agnostic logic are implemented in this class, and type-specific logic are extracted as abstract
        methods and implemented in add_docs_handler for individual types.
        
src/marqo/core/vespa_index/add_documents_handler.py