From 5aded967efa3b9a415ab51a9f451932a4868082c Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Thu, 16 Jul 2026 21:01:05 +0200 Subject: [PATCH] Gateway: Kontext-Limit-Warnung ignoriert Warm-Pings (max_tokens<=16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lucys Augen-Waermer (alle 10 min, max_tokens=1 an "vision"), warmup.sh und models/load enden konstruktionsbedingt mit finish_reason=length — die Warnung vom 15.07. hielt jeden Ping fuer einen abgerissenen Worker und spammte den Briefkasten (~alle 20 min, Serie 16.07. abends). Wer freiwillig auf <=16 Tokens deckelt, will keine echte Antwort -> keine Warnung; echte Abrisse melden weiter. Co-Authored-By: Claude Fable 5 --- backend/routers/gateway_proxy.py | 4 ++-- backend/services/gateway_stream.py | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/backend/routers/gateway_proxy.py b/backend/routers/gateway_proxy.py index e037501..0f0a6e0 100644 --- a/backend/routers/gateway_proxy.py +++ b/backend/routers/gateway_proxy.py @@ -239,7 +239,7 @@ async def _proxy(path: str, request: Request): async def gen(): try: async for chunk in r.aiter_raw(): - record_stream_chunk(chunk, alias) + record_stream_chunk(chunk, alias, body.get("max_tokens")) yield chunk finally: await r.aclose() @@ -251,7 +251,7 @@ async def _proxy(path: str, request: Request): 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) + warn_truncation(alias, body.get("max_tokens")) 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 43bbcf4..f605b58 100644 --- a/backend/services/gateway_stream.py +++ b/backend/services/gateway_stream.py @@ -22,8 +22,16 @@ log = logging.getLogger(__name__) _trunc_last: dict[str, float] = {} _TRUNC_EVERY = 600.0 # s +# Warm-Pings (Lucys Augen-Wärmer alle 10 min, warmup.sh, models/load) halten Modelle +# ABSICHTLICH mit max_tokens=1 warm — deren finish_reason=length ist konstruktionsbedingt +# und kein abgerissener Worker (Fehlalarm-Serie 16.07. abends, ~alle 20 min im Briefkasten). +# Wer freiwillig so knapp deckelt, will keine echte Antwort → keine Warnung. +_PING_MAXTOK = 16 -def warn_truncation(model: str) -> None: + +def warn_truncation(model: str, max_tokens: int | None = None) -> None: + if isinstance(max_tokens, int) and max_tokens <= _PING_MAXTOK: + return now = time.time() if now - _trunc_last.get(model, 0.0) < _TRUNC_EVERY: return @@ -50,11 +58,11 @@ def record_usage(usage: dict | None, model: str) -> None: increment_tokens(prompt, completion, model=model) -def record_stream_chunk(chunk: bytes, model: str) -> None: +def record_stream_chunk(chunk: bytes, model: str, max_tokens: int | None = None) -> 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) + warn_truncation(model, max_tokens) if b'"usage"' not in chunk: return text = chunk.decode("utf-8", errors="ignore")