ADR-007: Austauschbarer LLM-Provider
Warum Owlat das Vercel AI SDK mit einer Provider-Abstraktionsschicht nutzt, statt einen einzelnen LLM-Anbieter fest zu verdrahten.
- Status: Angenommen
- Datum: 2026-03-24
Kontext
Agent Pipeline, Knowledge Graph und das semantische Dateisystem benötigen allesamt LLM-Fähigkeiten — Textgenerierung, strukturierte Ausgaben, Embeddings und Tool Calling. Owlat muss mehrere Deployment-Szenarien unterstützen:
- Cloud-Nutzer, die über API-Schlüssel die besten verfügbaren Modelle (GPT-4o, Claude usw.) einsetzen möchten
- Selbst-Hoster, die einen vollständig offlinefähigen Betrieb mit lokalen Modellen (über Ollama, vLLM oder Ähnliches) benötigen
- Enterprise-Nutzer, die über interne API-Gateways mit eigenen Endpunkten routen
Die geprüften Optionen:
- OpenAI fest verdrahten — am einfachsten, schließt aber Selbst-Hoster aus, die Cloud-APIs nicht nutzen können oder wollen
- LangChain/LlamaIndex — schwergewichtige Frameworks mit großen Abhängigkeitsbäumen, komplexen Abstraktionen und Funktionen, die Owlat nicht braucht (Chains, Memory-Verwaltung, Vector-Store-Adapter)
- Vercel AI SDK mit Provider-Abstraktion — leichtgewichtig, bereits im Abhängigkeitsbaum vorhanden (
@ai-sdk/openai), anbieterunabhängig, unterstützt strukturierte Ausgaben und Tool Calling nativ
Entscheidung
Das Vercel AI SDK als LLM-Orchestrierungsschicht einsetzen, gekapselt in einer dünnen, über Umgebungsvariablen konfigurierten Provider-Abstraktion.
LLM_PROVIDER=openai # or: openrouter, ollama
LLM_BASE_URL= # for ollama: http://ollama:11434/v1 (localhost outside Docker)
LLM_API_KEY= # not needed for ollama; openrouter resolves its own base URL
LLM_MODEL_FAST=gpt-4o-mini # classify / extract / guard / summarize
LLM_MODEL_CAPABLE=gpt-4o # draft / plan
LLM_MODEL=gpt-4o # shared fallback when a tier var is unset
LLM_EMBEDDING_MODEL= # optional, defaults to text-embedding-3-small
Modelle werden pro Task-Stufe ausgewählt statt als ein einzelnes Modell — die Routing-Regeln beschreibt ADR-009: Aufgabenbasiertes Model-Routing. LLM_MODEL bleibt ein gemeinsamer Fallback, wenn die stufenspezifischen Variablen nicht gesetzt sind.
Die Factory createOpenAI() des AI SDK akzeptiert einen baseURL-Parameter, wodurch jede OpenAI-kompatible API (Ollama, vLLM, LiteLLM, Azure OpenAI) ohne zusätzlichen Provider-Code funktioniert. openrouter und ollama werden vom selben OpenAI-kompatiblen Provider mit einer vorab aufgelösten Basis-URL bedient.
Natives Anthropic über @ai-sdk/anthropic ist nicht implementiert und nicht installiert — nur @ai-sdk/openai ist eine Abhängigkeit in apps/api/package.json. Der Wert anthropic für LLM_PROVIDER wird nicht gesondert behandelt: Er landet im Default-Zweig von resolveBaseURL() (apps/api/convex/lib/llmProvider.ts:56-57), der undefined zurückgibt, sodass er sich wie einfaches OpenAI verhält. Um heute Claude-Modelle zu betreiben, nutzen Sie den OpenAI-kompatiblen Provider mit Anthropics OpenAI-kompatiblem Endpunkt:
LLM_PROVIDER=openai
LLM_BASE_URL=https://api.anthropic.com/v1/
LLM_MODEL_CAPABLE=claude-sonnet-4-6
LLM_MODEL_FAST=claude-haiku-4-5-20251001
Für Ollama setzen Sie ein lokales Modell, etwa LLM_MODEL_FAST=llama3.1:8b.
Die LLM-Auflösung liegt in einem einzigen Modul, apps/api/convex/lib/llmProvider.ts, das getLLMProvider(task), getLLMProviderForUserText(task, userText), getEmbeddingModel(), getLLMConfig() und assertEmbeddingDimension() exportiert. Es liest diese Umgebungsvariablen und ruft die Factory createOpenAI() des AI SDK direkt auf, mit einer je nach LLM_PROVIDER durch resolveBaseURL() (llmProvider.ts:48-59) aufgelösten baseURL. Jeder Consumer importiert und ruft diese Funktionen direkt auf.
Konsequenzen
Ermöglicht:
- Selbst-Hoster betreiben Ollama lokal für vollständig offlinefähige, kostenlose KI-Funktionen
- Cloud-Nutzer wählen ihren bevorzugten Anbieter (OpenAI, OpenRouter, Claude über den OpenAI-kompatiblen Endpunkt oder jede kompatible API)
- Enterprise-Nutzer zeigen über
LLM_BASE_URLauf interne Gateways oder Proxy-Endpunkte - Eine einzige Konfigurationsfläche — eine Handvoll Umgebungsvariablen (
LLM_PROVIDER,LLM_BASE_URL,LLM_API_KEY, die Modellstufen-Variablen undLLM_EMBEDDING_MODEL) steuert das gesamte LLM-Verhalten - Das AI SDK ist bereits eine Abhängigkeit (
@ai-sdk/openaisowohl inapps/apials auch inapps/web)
Abwägungen:
- Die Qualität unterscheidet sich zwischen Anbietern erheblich — lokale Modelle liefern womöglich schlechtere Klassifikationen und Entwürfe als GPT-4o oder Claude
- Embedding-Dimensionen unterscheiden sich zwischen Modellen — Vektorindizes müssen auf die Dimensionen des gewählten Embedding-Modells konfiguriert werden
- Keine eingebauten RAG-Chains — Retrieval-Augmented Generation ist als explizite Convex-Funktionsschritte umgesetzt (Vektorindex abfragen, Ergebnisse an den Prompt übergeben), was ausführlicher, aber besser debugbar ist
- AI-SDK-Updates können Breaking Changes einführen, auch wenn die Abstraktionsschicht den Anwendungscode isoliert