ADR-009: Aufgabenbasiertes Modell-Routing
Warum Owlat eine LLM-Modellauswahl pro Aufgabe unterstützt, statt ein einziges Modell für alle Pipeline-Schritte zu verwenden.
- Status: Angenommen
- Datum: 2026-03-24
Kontext
Die Agent-Pipeline, der Knowledge Graph und das Dateisystem führen allesamt LLM-Aufrufe durch — allerdings mit unterschiedlichen Anforderungen:
| Aufgabe | Anforderungen | Volumen |
|---|---|---|
| Klassifikation | Geschwindigkeit, strukturierte Ausgabe | Jede eingehende Nachricht |
| Sicherheitsfilter | Geschwindigkeit, geringe Latenz | Jede eingehende Nachricht |
| Wissensextraktion | Strukturierte Ausgabe, Kosteneffizienz | Jede verarbeitete Nachricht |
| Datei-Tagging | Zusammenfassung, Kosteneffizienz | Jede hochgeladene Datei |
| Handlungsplanung (geplant) | Reasoning, Tool-Nutzung | Pro Nachricht (nach der Klassifikation) |
| Entwurfserstellung | Schreibqualität, Tonalitätstreue | Pro Nachricht (wenn ein Entwurf benötigt wird) |
| Kontextverdichtung | Zusammenfassung | Wenn der Kontext das Budget überschreitet |
Die Aufgabe plan (Handlungsplanung) ist im Typ definiert und in taskTier() einem Tier zugeordnet, hat aber noch keine Aufrufstellen. Verdrahtet sind die Aufgaben classify, draft, extract, summarize und guard — guard läuft produktiv in apps/api/convex/agent/steps/security_scan/index.ts:88, die übrigen werden in apps/api/convex/agent/steps/, knowledge/extraction.ts, semanticFileProcessing.ts und translate.ts aufgerufen. Die Zeile plan beschreibt die beabsichtigte Nutzung, nicht das aktuelle Verhalten.
Alles auf dem leistungsfähigsten Modell laufen zu lassen (GPT-4o, Claude Sonnet) verschwendet Ressourcen — die Klassifikation liefert ein einfaches Enum und braucht nicht die Reasoning-Kapazität eines Frontier-Modells. Alles auf dem günstigsten Modell laufen zu lassen (GPT-4o-mini, Llama 3) spart Geld, erzeugt aber Entwürfe von schlechter Qualität.
Die betrachteten Optionen:
- Ein einziges Modell — die einfachste Konfiguration, erzwingt aber eine Wahl zwischen Qualität und Kosten
- Modellkonfiguration pro Schritt — fünf separate Modell-Umgebungsvariablen, aufwendig zu konfigurieren
- Zweistufiges Routing — schnelles Modell für strukturierte Aufgaben mit hohem Volumen, leistungsfähiges Modell für Reasoning und Textproduktion
Entscheidung
Den austauschbaren LLM-Provider um ein zweistufiges Modell-Routing erweitern:
LLM_MODEL=gpt-4o # Fallback for all tasks
LLM_MODEL_CAPABLE=gpt-4o # Drafting, planning, reasoning
LLM_MODEL_FAST=gpt-4o-mini # Classification, extraction, summarization
Aufrufer fordern ein Modell über task an, und die Routing-Regel ordnet jede Aufgabe dem Fast- oder dem Capable-Tier zu:
// apps/api/convex/lib/llmProvider.ts (illustrative — actual tier rule)
type LLMTask = 'classify' | 'extract' | 'guard' | 'summarize' | 'draft' | 'plan'
function taskTier(task: LLMTask): 'fast' | 'capable' {
// classify / extract / guard / summarize → fast
// draft / plan → capable
}
// OpenAI-compatible provider resolves the model ID for the tier
// (env reads go through the typed `getOptional()` wrapper, not raw process.env):
const modelId = tier === 'fast'
? getOptional('LLM_MODEL_FAST') ?? getOptional('LLM_MODEL') ?? 'gpt-4o-mini'
: getOptional('LLM_MODEL_CAPABLE') ?? getOptional('LLM_MODEL') ?? 'gpt-4o'
getLLMProvider(task) in apps/api/convex/lib/llmProvider.ts ist die Implementierung, die Aufrufer verwenden; die Fast/Capable-Entscheidung liegt in der privaten Funktion taskTier() in derselben Datei. Die Fallback-Semantik der Umgebungsvariablen steckt in modelIdForTier(), ebenfalls in apps/api/convex/lib/llmProvider.ts.
Sowohl LLM_MODEL_CAPABLE als auch LLM_MODEL_FAST fallen auf LLM_MODEL zurück, was bedeutet:
- Minimale Konfiguration: Setzen Sie nur
LLM_MODEL, und alles verwendet dieses Modell (dasselbe Verhalten wie in ADR-007) - Kostenoptimierung: Setzen Sie
LLM_MODEL_FAST=gpt-4o-miniundLLM_MODEL_CAPABLE=gpt-4o, um nach Aufgabe zu trennen - Einfachheit beim Self-Hosting: Self-Hoster, die ein einzelnes Ollama-Modell betreiben, setzen
LLM_MODEL=llama3, und beide Tiers verwenden es
Die oben genannten Modell-IDs (gpt-4o-mini / gpt-4o) sind die Standardwerte des OpenAI-kompatiblen Clients. LLM_PROVIDER (openai, openrouter oder ollama) wählt die Basis-URL; jeder Client ist OpenAI-kompatibel. Um Claude zu verwenden, belassen Sie LLM_PROVIDER auf dem Standardwert openai, richten LLM_BASE_URL auf einen OpenAI-kompatiblen Anthropic-Proxy und setzen anschließend LLM_MODEL_FAST / LLM_MODEL_CAPABLE auf die gewünschten Claude-Modell-IDs. Zur Provider-Schicht siehe ADR-007.
Konsequenzen
Ermöglicht:
- Kostensenkung um 60–80 % bei Aufgaben mit hohem Volumen (Klassifikation, Extraktion) durch den Einsatz kleinerer Modelle
- Qualitätserhalt bei Aufgaben, die ihn brauchen (Entwurfserstellung, Planung), durch den Einsatz leistungsfähiger Modelle
- Einfacher Upgrade-Pfad — mit einem Modell starten und später bei wachsendem Volumen aufteilen
- Self-Hoster können ein einzelnes Modell ohne jegliche Routing-Konfiguration betreiben
- Kostenverfolgung pro Schritt wird aussagekräftig (Aufrufe des schnellen Modells sind günstig, Aufrufe des leistungsfähigen Modells sind der eigentliche Kostentreiber)
Trade-offs:
- Zwei zusätzliche Umgebungsvariablen, die dokumentiert und unterstützt werden müssen
- Annahmen zur Modellkompatibilität — das Verhalten bei strukturierter Ausgabe unterscheidet sich je Modell und erfordert unter Umständen modellspezifische Prompt-Anpassungen
- Self-Hoster, die ein einzelnes kleines Modell verwenden, werden bei Entwurfsaufgaben Qualitätseinbußen sehen (derselbe Trade-off wie bereits in ADR-007)
- Noch keine Modellüberschreibung pro Organisation — das Routing gilt systemweit und ist nicht mandantenspezifisch konfigurierbar