From 3cf111d973e321c3aa069565229972fd320487c4 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Wed, 15 Jul 2026 14:43:36 +0200 Subject: [PATCH] Kontext-Limit sichtbar machen: Gateway-Warnung bei finish_reason=length + Etappen-Regel User-Wunsch 15.07. ('es sollte eine Warnung geben - Kontext ist nicht unendlich'): 4 P3-Worker starben still an finish_reason=length. - Gateway erkennt abgerissene Antworten (Stream-Chunk + Non-Stream) und meldet je Modell max. alle 10 min in den Briefkasten (silent -> Chronik), Zustellung per MC_ANNOUNCE_HTTP ans Steuerpult (Store bleibt Ein-Schreiber). - Steckbrief-Hook: KONTEXT-BUDGET & ETAPPEN-REGEL - gezielt lesen, grosse Aufgaben in Etappen schneiden (kanban_create/complete mit Plan) statt am Limit zu sterben. Co-Authored-By: Claude Fable 5 --- backend/routers/gateway_proxy.py | 8 ++++-- backend/services/gateway_stream.py | 29 +++++++++++++++++++++ deploy/agent-hooks/box-steckbrief-inject.sh | 1 + deploy/mc2-gateway.service | 3 +++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/backend/routers/gateway_proxy.py b/backend/routers/gateway_proxy.py index f941362..059de70 100644 --- a/backend/routers/gateway_proxy.py +++ b/backend/routers/gateway_proxy.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Request from fastapi.responses import JSONResponse, StreamingResponse from config import LLAMA_SWAP_URL -from services.gateway_stream import record_stream_chunk, record_usage +from services.gateway_stream import record_stream_chunk, record_usage, warn_truncation from services.router_logic import IMAGE_PART_TYPES, VISION_CAPABLE, choose_for_lane, has_image from services.routing_policy import load_policy @@ -201,7 +201,11 @@ async def _proxy(path: str, request: Request): r = await client.post(url, json=body, timeout=600.0) resp_json = r.json() - record_usage(resp_json.get("usage") if isinstance(resp_json, dict) else None, alias) + if isinstance(resp_json, dict): + record_usage(resp_json.get("usage"), alias) + if any(isinstance(c, dict) and c.get("finish_reason") == "length" + for c in resp_json.get("choices") or []): + warn_truncation(alias) return JSONResponse(resp_json, status_code=r.status_code, headers=routed) diff --git a/backend/services/gateway_stream.py b/backend/services/gateway_stream.py index 575749b..43bbcf4 100644 --- a/backend/services/gateway_stream.py +++ b/backend/services/gateway_stream.py @@ -7,11 +7,38 @@ verstreute, still scheiternde String-Suche durch einen testbaren SSE-Zeilenparse import json import logging +import time from services.token_stats import increment_tokens log = logging.getLogger(__name__) +# Kontext-Limit-Warnung (User-Wunsch 15.07.: „es sollte eine Warnung geben — Kontext ist +# nicht unendlich"): finish_reason=length heißt, eine Antwort ist am Token-/Kontext-Budget +# ABGERISSEN — bei Nacht-Workern stirbt damit still die halbe Arbeit (4 Worker am 15.07.). +# Statt still: Eintrag in den Briefkasten (silent → Chronik/Panel, kein Sprach-Spam), +# je Modell höchstens alle 10 min. Läuft im mc2-gateway-Prozess → announce liefert per +# MC_ANNOUNCE_HTTP beim Steuerpult ab (Unit-Env), nie direkt in die Store-Datei. +_trunc_last: dict[str, float] = {} +_TRUNC_EVERY = 600.0 # s + + +def warn_truncation(model: str) -> None: + now = time.time() + if now - _trunc_last.get(model, 0.0) < _TRUNC_EVERY: + return + _trunc_last[model] = now + log.warning("gateway: finish_reason=length bei %s — Antwort am Kontext-/Token-Limit abgerissen", model) + try: + from services import announce + announce.add( + f"Eine Antwort von „{model}“ ist am Token-/Kontext-Limit abgerissen " + f"(finish_reason=length). War das ein Nacht-Worker, ist seine Aufgabe zu groß " + f"geschnitten — besser in Etappen teilen (eine Etappe = ein Worker-Lauf).", + "[Kontext-Limit]", "gateway", "silent") + except Exception: + log.warning("gateway: Kontext-Limit-Warnung nicht zustellbar", exc_info=True) + def record_usage(usage: dict | None, model: str) -> None: """Ein usage-Objekt verbuchen (no-op bei None/leer).""" @@ -26,6 +53,8 @@ def record_usage(usage: dict | None, model: str) -> None: def record_stream_chunk(chunk: bytes, model: str) -> None: """Rohen SSE-Chunk auf `usage` prüfen und Tokens verbuchen. Fehler werden geloggt (debug) statt verschluckt — ein defekter Chunk bricht den Stream nicht.""" + if b'"finish_reason":"length"' in chunk or b'"finish_reason": "length"' in chunk: + warn_truncation(model) if b'"usage"' not in chunk: return text = chunk.decode("utf-8", errors="ignore") diff --git a/deploy/agent-hooks/box-steckbrief-inject.sh b/deploy/agent-hooks/box-steckbrief-inject.sh index fa2cef9..2d36fba 100644 --- a/deploy/agent-hooks/box-steckbrief-inject.sh +++ b/deploy/agent-hooks/box-steckbrief-inject.sh @@ -32,6 +32,7 @@ Die Modelle / das "Hirn" laufen ausschliesslich ueber den Router llama-swap auf PROTOKOLL-PFLICHT (15.07.): Beende deine Aufgabe IMMER mit dem Tool-Aufruf kanban_complete (fertig) oder kanban_block (Frage/Sackgasse). Eine Text-Zusammenfassung OHNE diesen Aufruf zaehlt als Absturz (Protokollverstoss) und blockiert die Karte — genau so ging am 14.07. eine fertige Arbeit verloren. REPO-WEGWEISER mission-control-v2 (NICHT raten/suchen — so liegt es): backend/app.py = Einstieg + Router-Mounting · backend/routers/*.py = REST-Endpunkte · backend/services/*.py = Logik · backend/gateway_app.py = /v1-Gateway-Prozess (:9010) · frontend/src/views/*.tsx = Ansichten · frontend/src/lib/api.ts = Typen+Fetch · frontend/src/lib/queries.ts = TanStack-Query-Hooks (Polling) · frontend/src/nav.ts = Sidebar · deploy/ = Skripte/Units/Hooks/Skills · Projekt-Regeln: AGENTS.md (Wurzel) + docs/wissen/. FRONTEND-WAHRHEIT: Die Box hat KEIN node/npm — du kannst frontend/dist NICHT bauen. Bei Frontend-Aenderungen: NUR frontend/src schreiben und im Vorschlag/Abschluss EHRLICH vermerken "dist muss am PC gebaut werden (npm run build)" — sonst zeigt die Box nach Annahme den alten Stand. +KONTEXT-BUDGET & ETAPPEN-REGEL (15.07. — vier Worker starben an finish_reason=length): Dein Kontext ist ENDLICH. Lies gezielt ueber den Wegweiser statt ls-Ketten und Volltexte; grosse Dateien nur in Ausschnitten. Merkst du, dass die Aufgabe mehr als ~5 Dateien oder mehrere Baustellen umfasst: NICHT durchziehen — schneide Etappen (eine Etappe = ein Worker-Lauf): lege Teil-Karten an (kanban_create, falls verfuegbar) und schliesse deine Karte per kanban_complete mit dem Etappen-Plan ab; alternativ kanban_block mit dem Etappen-Vorschlag als Frage. EOF )" diff --git a/deploy/mc2-gateway.service b/deploy/mc2-gateway.service index a5ea4c5..9ee37c8 100644 --- a/deploy/mc2-gateway.service +++ b/deploy/mc2-gateway.service @@ -19,6 +19,9 @@ Environment=PYTHONPATH=%h/mission-control-v2 Environment=MC_LLAMA_SWAP_URL=http://127.0.0.1:8080 Environment=MC_CONFIG_PATH=/etc/llama-swap/config.yaml Environment=MC_MODELS_DIR=/srv/models +# Briefkasten-Meldungen (z. B. Kontext-Limit-Warnung) NIE direkt in die Store-Datei — +# die gehört exklusiv dem Steuerpult; per HTTP abliefern (gleiches Muster wie mc2-steward). +Environment=MC_ANNOUNCE_HTTP=http://127.0.0.1:9001 Restart=always RestartSec=2