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 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Request
|
|||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
|
|
||||||
from config import LLAMA_SWAP_URL
|
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.router_logic import IMAGE_PART_TYPES, VISION_CAPABLE, choose_for_lane, has_image
|
||||||
from services.routing_policy import load_policy
|
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)
|
r = await client.post(url, json=body, timeout=600.0)
|
||||||
resp_json = r.json()
|
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)
|
return JSONResponse(resp_json, status_code=r.status_code, headers=routed)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,38 @@ verstreute, still scheiternde String-Suche durch einen testbaren SSE-Zeilenparse
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
from services.token_stats import increment_tokens
|
from services.token_stats import increment_tokens
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
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:
|
def record_usage(usage: dict | None, model: str) -> None:
|
||||||
"""Ein usage-Objekt verbuchen (no-op bei None/leer)."""
|
"""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:
|
def record_stream_chunk(chunk: bytes, model: str) -> None:
|
||||||
"""Rohen SSE-Chunk auf `usage` prüfen und Tokens verbuchen. Fehler werden
|
"""Rohen SSE-Chunk auf `usage` prüfen und Tokens verbuchen. Fehler werden
|
||||||
geloggt (debug) statt verschluckt — ein defekter Chunk bricht den Stream nicht."""
|
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:
|
if b'"usage"' not in chunk:
|
||||||
return
|
return
|
||||||
text = chunk.decode("utf-8", errors="ignore")
|
text = chunk.decode("utf-8", errors="ignore")
|
||||||
|
|||||||
@@ -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.
|
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/.
|
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.
|
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
|
EOF
|
||||||
)"
|
)"
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ Environment=PYTHONPATH=%h/mission-control-v2
|
|||||||
Environment=MC_LLAMA_SWAP_URL=http://127.0.0.1:8080
|
Environment=MC_LLAMA_SWAP_URL=http://127.0.0.1:8080
|
||||||
Environment=MC_CONFIG_PATH=/etc/llama-swap/config.yaml
|
Environment=MC_CONFIG_PATH=/etc/llama-swap/config.yaml
|
||||||
Environment=MC_MODELS_DIR=/srv/models
|
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
|
Restart=always
|
||||||
RestartSec=2
|
RestartSec=2
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user