One interface, many models
The adapter pattern hides provider-specific shape behind a single interface. Your application code calls adapter.generate_stream(messages); the adapter knows which API to hit, how to format the request, and how to normalize the response.
Why bother
- Swap models without changing app code — flip Gemini to OpenAI to local Ollama by changing one line.
- Build fallback chains — if Gemini errors, try OpenAI, then local. The chain is a list of adapters.
- A/B test providers — route 10% of traffic to a new model behind the same interface.
- Translate prompt formats — abstract over per-provider quirks (system prompt placement, tool-call shape, role names).
Where to draw the abstraction line
Narrow. Abstract only the streaming generate call and (maybe) the tool loop. Don't try to abstract every provider feature — file APIs, caching, embeddings — into a unified interface. The cost of an over-broad abstraction is that every new provider requires shoehorning. Keep the seam at generate_stream and let provider-specific features stay provider-specific.