Build a local AI feature
Keep the provider swappable
Define a small application interface so local and cloud implementations can change without leaking provider details through the product.
Do not let the entire application depend on Ollama response objects.
Define the operation your product needs:
type ActivitySummary = {
summary: string
confidence: 'low' | 'medium' | 'high'
}
type Summarizer = {
summarize(activity: Activity): Promise<ActivitySummary>
}
An Ollama implementation can call the local API. A cloud implementation can call a provider. A deterministic implementation can support tests and fallback behavior.
Normalize errors and output at the adapter boundary. The UI should not need to know whether a provider calls generated text message.content, output_text, or something else. Map everything to ActivitySummary before it leaves the adapter module.
Swappable does not mean identical. Local and cloud models can differ in capabilities, privacy, latency, and cost. The interface preserves application structure while your evaluation decides whether each implementation is acceptable.
I keep three files: summarizer.ts for the interface, ollama-summarizer.ts for the local adapter, and deterministic-summarizer.ts for tests. Dependency injection picks one at startup based on config.
When you add a cloud adapter later, reuse the same validator and fallback rules. Shape and truth checks belong in shared code, not duplicated per provider.
Configuration should name the active implementation and the model tag. Switching providers becomes an ops change, not a refactor across React components.
The deterministic implementation is not a second-class citizen. It is the behavior your tests and offline mode rely on every day.
Register the implementation in one place:
const summarizer = config.provider === 'ollama'
? createOllamaSummarizer(config.model)
: createDeterministicSummarizer()
The rest of the app imports summarizer.summarize() and nothing else from the AI layer.
Try this on your own project: write the interface first and make the UI call it with a fake implementation that returns fixed text. If the screen works before Ollama is wired, your boundary is in the right place.
Lesson completed