code: def run_exp(
      entry_dict: Dict[str, Any],
      copy_paths: Optional[List[str]] = None,
      message: Optional[str] = None,
  ) -> None:
      """Run a full experiment.
  
      Experiment subtasks are executed inline as one atomic operation.
  
      Arguments:
          entry_dict: Serialized QueueEntry for this experiment.
      """
      from dvc.repo import Repo
  
      entry = QueueEntry.from_dict(entry_dict)
      with Repo(entry.dvc_root) as repo:
          queue = repo.experiments.celery_queue
          infofile = queue.get_infofile_path(entry.stash_rev)
      executor = setup_exp.s(entry_dict)()
      try:
          cmd = ["dvc", "exp", "exec-run", "--infofile", infofile]
          if copy_paths:
              for path in copy_paths:
                  cmd.extend(["--copy-paths", path])
          if message:
              cmd.extend(["--message", message])
          proc_dict = queue.proc.run_signature(
              cmd, name=entry.stash_rev
          )()
          collect_exp.s(proc_dict, entry_dict)()
      finally:
          cleanup_exp.s(executor, infofile)()
  
file_path: dvc/dvc/repo/experiments/queue/tasks.py
imports: from typing import TYPE_CHECKING, Any, Dict, List, Optional
  from celery import shared_task
  from celery.utils.log import get_task_logger
  from dvc.repo.experiments.executor.base import ExecutorInfo
  from dvc.repo.experiments.executor.local import TempDirExecutor
  from .base import BaseStashQueue, QueueEntry
label_desc: Use image node_id 4 for calling a global function with example usage: run_exp(entry_dict, copy_paths, message) without return types