Provider-routing forklaret
Sådan vælger Cartwright mellem Anthropic og Local — per-intent + per-config.
Hver gang Cartwright skal kalde en AI-model, går den gennem chatModel(intent) i lib/ai/client.ts. Det er ét enkelt sted hvor routing-logikken bor.
Decision tree
chatModel(intent="vibe") → ALTID Anthropic
chatModel(intent="chat" eller "generation"):
aiProvider="anthropic" → Anthropic
aiProvider="local" → Local (Ollama)
aiProvider="auto":
local konfigureret → Local
ellers → Anthropic
on-error (Fase 2 fremtid) → Local → Anthropic fallbackHvorfor vibe altid bruger Anthropic
Tre steder i Cartwright bruger chatModel("vibe"):
lib/ai/theme-generator.ts— genererer 6-farve palette + rationale (structured output)lib/ai/product-seo-generator.ts— produkt-beskrivelse + attributter (structured output)lib/ai/category-seo-generator.ts— kategori-SEO + FAQ (structured output)
Alle bruger generateObject() med et Zod-schema. Hvis modellen returnerer JSON der ikke matcher schemaet, fejler hele wizard-flowet. Det er en høj-stake operation hvor kvalitet matters mere end privacy.
Lokal Gemma kan godt generere JSON — men ikke pålideligt nok endnu til at vi ville udsætte en kunde for at "wizarden fejlede tilfældigt, prøv igen". Når Gemma 5 lander og kan håndtere det, fjerner vi vibe-undtagelsen i én if-condition.
Auto-mode i detalje
aiProvider = "auto" siger til Cartwright: "prøv local hvis det er konfigureret; ellers cloud".
I v1 er auto-mode "prefer local hvis configured" — der er ingen request-time fallback når local er nede. Hvis Ollama er nede og du har auto-mode, ser admin en fejl i chatten og kan manuelt skifte til Anthropic.
I Fase 2 (på roadmap) tilføjer vi localAiFallbackMode = "on-error" som lever af request-time fejldetection: hvis et streamText-kald fejler, retries automatisk mod Anthropic, og lastDegradedAt skrives så status-pill viser "Auto · degraded".
Audit-log
Hvert tool-call skrives med provider og model (Local-AI plan udvidede AuditLog-modellen). Det betyder du kan eftervise overfor DPO at den specifikke ordre-update eller customer-lookup gik til lokal eller cloud:
-- Hvor mange admin-actions gik til local AI sidste uge?
SELECT provider, COUNT(*) FROM AuditLog
WHERE createdAt > date('now', '-7 days') AND actor LIKE 'operator-chat:%'
GROUP BY provider;Eller direkte i admin: /admin/audit har provider/model-kolonner.
Hvordan tilføjer jeg en ny provider?
chatModel() har en switch over aiProvider. Vil du tilføje fx Mistral, DeepSeek eller en ny lokal model:
- Tilføj enum-værdi til
AiProviderilib/ai/settings.ts - Tilføj case i
chatModel()switch — typiskcreateOpenAICompatible(...)mod den nye provider's base-URL - Tilføj modellen til
MODEL_CAPABILITIESmed dens tool-tier - Tilføj UI-radio i
LocalAiForm.tsx
Hele kæden er ~30 minutter for en ny provider. Det er extensibility som infrastruktur — ikke et nyt plugin-framework.
Hvorfor ingen "request-time provider switch"?
Du kan tænke: hvorfor lader Cartwright ikke mig vælge provider per chat-message? Svaret: confirmation-token-systemet og audit-log er request-scoped, og at skifte provider midt i en session ville gøre svært at debugge "hvilken model gjorde præcis dette".
Hvis du vil sammenligne providers, gå til /admin/integrations/ai-test og kør de samme prompts på begge konfigurationer. Det er en bevidst trade-off: ÉN provider per session = audit-clarity, sammenligning sker i et dedikeret testing-flow.