Provider abstraction shape

A good abstraction is small. Every method you add forces every backend to implement it, and every quirk of every backend leaks back into the seam. The chat function is the spine. Everything else is optional.

What lives inside vs outside the seam

The interface owns response normalisation, role mapping, and usage metadata. It does not own caching, prompt templates, or retry policy.

utils/llm_provider.py
python
# Two methods is the whole spine.
class LLMProvider(ABC):
    @abstractmethod
    async def chat(self, messages: list[Message], model: str) -> ChatResult:
        ...

    @abstractmethod
    async def stream(self, messages: list[Message], model: str) -> AsyncIterator[str]:
        ...

# Anything else lives at the call site, not in the base.
# That keeps the surface area small enough to add a new backend in an afternoon.

Two abstract methods, no helpers, no hooks. New backends focus on one thing: speaking the contract. Cross-cutting concerns wrap the seam from outside.

Quiz: Quiz

Loading practice…