run_dict["extra"] = extra
        try:
            self.client.create_run(**run_dict, project_name=self.project_name)
        except Exception as e:
            # Errors are swallowed by the thread executor so we need to log them here
            log_error_once("post", e)
            raise
    def _update_run_single(self, run: Run) -> None:
        """Update a run."""
        try:
            run_dict = run.dict()
            run_dict["tags"] = self._get_tags(run)
            self.client.update_run(run.id, **run_dict)
        except Exception as e:
            # Errors are swallowed by the thread executor so we need to log them here
            log_error_once("patch", e)
            raise
    def _submit(self, function: Callable[[Run], None], run: Run) -> None:
        """Submit a function to the executor."""
        if self.executor is None:
            function(run)
        else:
            self._futures.add(self.executor.submit(function, run))
    def _on_llm_start(self, run: Run) -> None:
        """Persist an LLM run."""
        if run.parent_run_id is None:
            run.reference_example_id = self.example_id
        self._submit(self._persist_run_single, run.copy(deep=True))
    def _on_chat_model_start(self, run: Run) -> None:
        """Persist an LLM run."""
        if run.parent_run_id is None:
            run.reference_example_id = self.example_id
        self._submit(self._persist_run_single, run.copy(deep=True))
    def _on_llm_end(self, run: Run) -> None:
        """Process the LLM Run."""
        self._submit(self._update_run_single, run.copy(deep=True))