from abc import ABC, abstractmethod


class LLMProvider(ABC):
    """Provider-agnostic interface every LLM backend must implement.

    Every service (itinerary generation, reply drafting, ...) is written against this
    interface only - never against a vendor SDK directly. Swapping OpenAI for Anthropic,
    or cloud for a local Ollama model, is a one-line change in providers/factory.py and
    an env var; nothing in app/services/ or app/prompts.py has to change.
    """

    @abstractmethod
    def complete_json(self, system_prompt: str, messages: list[dict]) -> dict:
        """Run one completion and return the parsed JSON object the model produced.

        `messages` is a list of {"role": "user"|"assistant", "content": str} - the
        system prompt is passed separately since not every provider's wire format
        allows a "system" entry inside the messages list (Anthropic doesn't).

        Raises whatever the underlying SDK raises on a transport/API error, and
        json.JSONDecodeError if the model's output can't be parsed as JSON even after
        the implementation's own cleanup - callers are expected to catch and wrap both.
        """
        raise NotImplementedError
