Welle 1 Tag 4 — Routing-Hook-Handoff
Stand zum 2026-05-17 abends
- Branch:
feat/welle1-tag4-backend-routingin~/source/apps/open-webui-vf/, noch nicht committed, working tree hat den ganzen Tag-4-Code drin. - Production: 3 mal deployed (Revisions 17 failed, 18 silent, 19 live mit DEBUG-Logs). Service HEALTHY.
- Drei Bedrock-Modelle exposed in LiteLLM (
claude-sonnet-4-6,vf-haiku-backend,vf-opus-backend).MODEL_FILTER_LISTversteckt die Backend-Modelle vor User. - CloudWatch-Alarm scaffolding (LogMetricFilter
vf-classifier-failed+ SNS-Alarm anav-ops-alerts) angelegt — feuert aber nie weil Hook nicht laeuft. - AVV + ZDR-Mail erweitert um Haiku 4.5 + Opus 4.7 (siehe avv-2026-openwebui-vf + 2026-05-17-zdr-addendum-anfrage).
Problem: Der Pre-Klassifikator-Hook VFClassifyRouter wird sauber geladen (taucht in litellm.callbacks auf), aber async_pre_call_hook wird bei OWUI-/chat/completions-Anfragen nie aufgerufen. Sonnet wird direkt angesprochen, kein Klassifikator-Routing, keine Routing-Entscheidung in Logs.
Was wir verifiziert haben (= nicht das Problem)
- Hook-Code laedt sauber. Modul importiert, Klasse instantiiert,
proxy_handler_instanceexposed. Validiert via lokalemimportlib.util.spec_from_file_locationTrockenlauf gegen das synthetisierte CloudFormation-Template. - LiteLLM-Imports stimmen (
litellm.integrations.custom_logger.CustomLogger,litellm.proxy._types.UserAPIKeyAuth,litellm.caching.caching.DualCache). Lokal inpip install litellmvenv getestet, alle Pfade existieren. - Config-File wird geparst und Hook landed in
litellm.callbacks. Lokal mit identischem YAML +initialize_callbacks_on_proxy()reproduziert — Output:[..., <vf_classify_route.VFClassifyRouter object at 0x...>]. Im Production-DEBUG-Log sichtbar alsInitialized Callbacks - [<vf_classify_route.VFClassifyRouter ...>]. - Alle 4 Gate-Bedingungen aus
utils.py:1432-1438passen lokal:isinstance(_callback, CustomLogger)→ True"async_pre_call_hook" in vars(_callback.__class__)→ True (vars(VFClassifyRouter) == ['async_pre_call_hook'])_callback.__class__.async_pre_call_hook != CustomLogger.async_pre_call_hook→ Truecall_typeist nichtcall_mcp_tool→ True
Was wir NICHT verstanden haben (= das eigentliche Problem)
proxy_logging_obj.pre_call_hookwird im Container offenbar NIE aufgerufen fuer/chat/completionsvon OWUI.- Indiz: KEIN
PROXY_PRE_CALL-Log-Eintrag, KEINservice_logging_obj.async_service_success_hookEintrag (das wuerdeutils.py:1461machen sobald irgendein Hook in der Schleife durchlaeuft, > 10ms). - Auch die anderen built-in pre_call_hooks (
_PROXY_MaxBudgetLimiter,_PROXY_MaxParallelRequestsHandler_v3,_PROXY_MaxIterationsHandler) zeigen keine Hook-Activity in den Logs — nur ihrencallback_controls.py:38“is disabled” Check. Das ist eine viel oberflaechlichere Schicht als der Pre-Call-Hook-Loop. - Code-Pfad sollte sein:
chat_completion()(proxy_server.py:8106) →ProxyBaseLLMRequestProcessing.base_process_llm_request()(common_request_processing.py:1001) →proxy_logging_obj.pre_call_hook()(Zeile 943).
Drei Hypothesen die getestet werden muessen:
- Pre-Call-Hooks brauchen einen anderen Registrierungs-Mechanismus als
litellm_settings.callbacks. Vielleicht muss mangeneral_settings.proxy_hook_modulessetzen oder den Hook ueberlitellm.proxy.proxy_server.proxy_logging_obj.callbacks.append()explizit registrieren statt nur inlitellm.callbacks. LITELLM_MODE=PRODUCTIONskippt Pre-Call-Hooks aus Performance-Gruenden. Aktuelle Container-env hat das gesetzt (open-webui-vf-stack.tsLiteLLM-Containerenvironment.LITELLM_MODE). Wenn man das aufDEVELOPMENToder weglaesst, koennte der Hook greifen.- OWUI nutzt einen anderen Code-Pfad (z.B.
/v1/messagesAnthropic-Format oder eine direkte Router-Bypass-Route), derbase_process_llm_requestumgeht. Lass uns die exakte Request-URL pruefen die OWUI zu LiteLLM schickt — das war in den Logs nicht explizit sichtbar.
Auftrag fuer naechste Session
Phase 1 — Diagnose (max 90 Min, dann stoppen):
- Lokal in open-webui-vf-stack.ts das LiteLLM-Container-Command rausholen, identisches Setup in einem Test-Container starten. Eine Test-Anfrage gegen
/v1/chat/completionsschicken und mitbreakpoint()ODER mitLITELLM_LOG=DEBUG+verbose_logger.debug(...)Patches inlitellm.proxy.utils.pre_call_hookrausfinden ob die Methode aufgerufen wird. - Pruefen welche konkrete URL OWUI an LiteLLM schickt fuer Chat-Completions (sollte
POST /v1/chat/completionssein, aber Verifikation). CloudWatch-Logs nach `“POST /v1/” filter pattern haben das gezeigt — aber ich war nicht 100% sicher. - Pruefen ob
LITELLM_MODE=PRODUCTIONeinen Side-Effect auf pre_call_hooks hat: GitHub-Issue-Search aufBerriAI/litellmmit Stichwortenpre_call_hook PRODUCTION mode silent+callbacks not triggering custom_logger. - Falls Diagnose abschliessend zeigt dass
pre_call_hookeinfach nicht aufgerufen wird (Bug oder Design): direkt zu Phase 3.
Phase 2 — Alternative-Mechanismen evaluieren (parallel, max 60 Min):
Folgende Alternativen mit Aufwand + Risiko + Stack-Fit bewerten:
| Alternative | Wie | Aufwand | Stack-Fit | Risiko |
|---|---|---|---|---|
| A. Guardrails-API statt callbacks | Hook als litellm_settings.guardrails: [{guardrail_name: vf-classify, callbacks: [vf_classify_route.proxy_handler_instance], mode: pre_call}] registrieren. Das geht durch _maybe_execute_pipelines was im Code VOR dem callback-Loop kommt. | 30 Min | gut — same code | mittel — Guardrails-API tendenziell stabil aber semantisch fuer Moderation, evtl Edge-Cases |
| B. LiteLLM Router-Rules | LiteLLM hat eingebauten Router mit routing_strategy und Model-Aliases. Statt Hook: definiere vf-sonnet als Router-Group mit 3 Modellen + Routing-Rule “wenn user_message matched X dann Y”. | 1-2 Std | sehr gut — native Feature | niedrig — etabliertes Pattern |
| C. Pre-Service vor LiteLLM | FastAPI-Container ZWISCHEN OWUI und LiteLLM. Macht den Klassifikator-Call, modifiziert data["model"], forwarded zu LiteLLM. Stack-Sidecar wie Cloudflared. | 1-2 Tage | sehr gut — voll kontrolliert, kein LiteLLM-Magic | niedrig |
| D. Custom Model in OWUI mit Logic | OWUI’s Custom-Model vf-sonnet koennte ein “Filter”-Plugin haben das pre-completion eine Routing-Entscheidung trifft. OWUI hat Plugin-System fuer Filters. | 2-3 Std | mittel — OWUI-Plugin-Wartung | mittel — OWUI-Updates koennten Plugin-API brechen |
| E. Routing-Logic in System-Prompt | KEIN externer Klassifikator. Stattdessen: System-Prompt enthaelt Anweisung “wenn die Anfrage trivial ist, antworte kurz; wenn komplex, denke laenger nach”. Sonnet macht beides selber. | 0 Min | trivial | hoch — keine echte Cost-Optimierung, nur UX |
| F. Aufgeben fuer Welle 1 | Tag 4 = nur Modelle exposed + Alarm-Infra. Routing zur Welle 2 vertagen. | 5 Min | n/a | n/a |
Phase 3 — Entscheidung + Umsetzung:
Marvin praesentieren: gefundenes Root-Cause + die 2-3 sinnvollsten Optionen mit konkretem Effort. Empfehlung mit Begruendung. Erst nach Marvins OK umsetzen.
Wichtige Files / Pfade
| Datei | Zweck |
|---|---|
~/source/apps/open-webui-vf/infra/lib/open-webui-vf-stack.ts | CDK-Stack mit dem LiteLLM-Container-Command und dem inline-Heredoc Hook-Code |
/private/tmp/litellm-test/ | venv mit litellm==1.85.0 fuer lokale Repro (gleiche Version wie Container) |
/Users/marvinkuehlmann/source/apps/open-webui-vf/infra/cdk.out/OpenWebUiVf.template.json | Synthesized template, hier kann man command[0] extrahieren um den Heredoc-Inhalt 1:1 zu rekonstruieren |
CloudWatch LogGroup /aws/ecs/default/open-webui-vf | Live-Logs. Filter vf_classify_route zeigt Hook-Init. Filter _PROXY_MaxBudgetLimiter als built-in-Vergleich. |
ECS Cluster default, Service open-webui-vf, TaskDef revision 19 | Aktuell laufende Version mit Tag-4-Code + DEBUG-Logs |
AWS Profile av-prod (= av-production) | Account 425924867359, eu-central-1 |
| welle-1-perfektion (Tag 4, Zeilen 530-668) | Original-Plan, beschreibt das gewuenschte Verhalten |
Was NICHT angefasst werden sollte
- Die bereits-deployten Aenderungen (Modelle, MODEL_FILTER, Alarm, AVV/ZDR-Doku-Updates) sind alle korrekt und sollen drinbleiben. Nur der Hook ist defekt.
- DEBUG-Log-Level (
LITELLM_LOG=DEBUGim Container) kann nach Closure aufINFOzurueck, aber waehrend der Investigation bitte drin lassen. - AVV + ZDR-Mail-Drafts sind so an Marvin freigegeben — bitte nicht selbst veraendern.
Done-Kriterien fuer naechste Session
- Entweder: Pre-Klassifikator triggert nachweislich (CloudWatch zeigt
vf_classify_route: claude-sonnet-4-6 -> vf-haiku-backend (cls=ROUTE)bei einer trivialen Test-Anfrage) UND alle 5 Routing-Eval-Cases (021-025) zeigen Treffer-Rate > 80 %. - Oder: Marvin hat entschieden welche Alternative (B/C/D/E/F) gewaehlt wird und der entsprechende Branch/Commit ist sauber.
- Plus: Welle-1-Run-Akte in
intern/runs/2026-05-XX-welle-1-completion/mit Lessons-Learned.
Lessons-Learned die JETZT schon sicher sind
- LiteLLM-Pre-Call-Hooks sind in v1.85 nicht-trivial zu verdrahten. Doku zeigt scalar
callbacks: my.module.instance, aber das fuehrt nicht zwingend dazu dass der Hook auch aufgerufen wird. Naechstes Mal: vor Code-Schreiben einen Smoke-Test mit der minimalsten Hook-Klasse (logger.info im Hook) machen. - Pre-Check-Pattern mit
importlib.util.spec_from_file_locationist gut — fing v1 (falscher Pfad) und v2 (falsche Imports) frueh ab. Beibehalten. set -euim Container-Command rettete uns mehrfach vor stillen Halb-Crashes. Pattern fuer alle zukuenftigen Inline-Heredoc-Container.- Cross-Reviewer-Agreement aus ce:review ist wertvoll — adv-005 (set -eu) hat genau dieses Problem vorhergesagt das im v1-Deploy auftrat.
MODEL_FILTER_LISTist robuster als API-Hiding — deklarativ, kein Race-Window, kein Admin-Key-Bedarf.