Here’s the refactored package structure with each module following the single-responsibility principle, proper relative imports, and a clean public API.

### `queue/__init__.py`
```python
"""Job Queue Package - Public API"""
from .core import Job, JobQueue
from .priority import PriorityQueue
from .retry import process_with_retry

__all__ = ["Job", "JobQueue", "PriorityQueue", "process_with_retry"]
```

### `queue/core.py`
```python
"""Core data model and basic FIFO queue operations."""
from dataclasses import dataclass
from typing import Any, Optional
from collections import deque
import threading


@dataclass
class Job:
    """Represents a unit of work to be processed."""
    id: str
    payload: Any
    priority: int = 0
    status: str = "pending"
    retries: int = 0


class JobQueue:
    """Thread-safe FIFO job queue."""
    def __init__(self) -> None:
        self._queue = deque()
        self._lock = threading.Lock()

    def enqueue(self, job: Job) -> None:
        with self._lock:
            self._queue.append(job)

    def dequeue(self) -> Optional[Job]:
        with self._lock:
            return self._queue.popleft() if self._queue else None

    def peek(self) -> Optional[Job]:
        with self._lock:
            return self._queue[0] if self._queue else None

    def size(self) -> int:
        with self._lock:
            return len(self._queue)

    def is_empty(self) -> bool:
        with self._lock:
            return len(self._queue) == 0
```

### `queue/priority.py`
```python
"""Priority queue implementation using a min-heap."""
import heapq
import threading
from typing import Optional
from .core import Job


class PriorityQueue:
    """Priority-based job queue. Lower priority values = higher priority."""
    def __init__(self) -> None:
        self._heap = []
        self._counter = 0  # Tie-breaker to preserve FIFO order for equal priorities
        self._lock = threading.Lock()

    def enqueue(self, job: Job) -> None:
        with self._lock:
            entry = (job.priority, self._counter, job)
            self._counter += 1
            heapq.heappush(self._heap, entry)

    def dequeue(self) -> Optional[Job]:
        with self._lock:
            if not self._heap:
                return None
            _, _, job = heapq.heappop(self._heap)
            return job

    def peek(self) -> Optional[Job]:
        with self._lock:
            if not self._heap:
                return None
            return self._heap[0][2]

    def size(self) -> int:
        with self._lock:
            return len(self._heap)

    def is_empty(self) -> bool:
        with self._lock:
            return len(self._heap) == 0
```

### `queue/retry.py`
```python
"""Retry logic with exponential backoff."""
import time
import logging
from typing import Callable, Any
from .core import Job

logger = logging.getLogger(__name__)


def process_with_retry(
    job: Job,
    process_func: Callable[[Job], Any],
    max_retries: int = 3,
    base_delay: float = 1.0,
    backoff_factor: float = 2.0
) -> Any:
    """
    Process a job with exponential backoff retry logic.
    
    Args:
        job: The job to process
        process_func: Callable that processes the job
        max_retries: Maximum number of retry attempts
        base_delay: Initial delay in seconds between retries
        backoff_factor: Multiplier for delay after each failed attempt
        
    Returns:
        Result from process_func on success
        
    Raises:
        Exception: The last exception raised if all retries are exhausted
    """
    last_exception = None
    for attempt in range(max_retries + 1):
        try:
            job.status = "processing"
            result = process_func(job)
            job.status = "completed"
            return result
        except Exception as e:
            last_exception = e
            job.retries += 1
            logger.warning(f"Job {job.id} failed (attempt {attempt + 1}/{max_retries + 1}): {e}")
            
            if attempt < max_retries:
                delay = base_delay * (backoff_factor ** attempt)
                logger.info(f"Retrying job {job.id} in {delay:.2f}s...")
                time.sleep(delay)
            else:
                job.status = "failed"
                
    raise last_exception
```

### Key Design Choices:
1. **Single Responsibility**: 
   - `core.py` handles data modeling and basic FIFO operations
   - `priority.py` isolates heap-based ordering logic
   - `retry.py` encapsulates only retry/backoff behavior
2. **Thread Safety**: Both queue implementations use `threading.Lock` to prevent race conditions in concurrent environments
3. **Consistent Interface**: `JobQueue` and `PriorityQueue` share the same public methods (`enqueue`, `dequeue`, `peek`, `size`, `is_empty`) for easy swapping
4. **Clean Public API**: `__init__.py` exposes only the intended symbols via `__all__`, keeping the package namespace tidy
5. **Relative Imports**: All internal imports use `.` prefix to ensure correct resolution when installed as a package

You can now use the package like:
```python
from queue import Job, JobQueue, PriorityQueue, process_with_retry

q = PriorityQueue()
q.enqueue(Job(id="1", payload={"task": "send_email"}, priority=1))
job = q.dequeue()
process_with_retry(job, process_func=my_handler)
```