Gateway: Kontext-Limit-Warnung ignoriert Warm-Pings (max_tokens<=16)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -239,7 +239,7 @@ async def _proxy(path: str, request: Request):
|
|||||||
async def gen():
|
async def gen():
|
||||||
try:
|
try:
|
||||||
async for chunk in r.aiter_raw():
|
async for chunk in r.aiter_raw():
|
||||||
record_stream_chunk(chunk, alias)
|
record_stream_chunk(chunk, alias, body.get("max_tokens"))
|
||||||
yield chunk
|
yield chunk
|
||||||
finally:
|
finally:
|
||||||
await r.aclose()
|
await r.aclose()
|
||||||
@@ -251,7 +251,7 @@ async def _proxy(path: str, request: Request):
|
|||||||
record_usage(resp_json.get("usage"), alias)
|
record_usage(resp_json.get("usage"), alias)
|
||||||
if any(isinstance(c, dict) and c.get("finish_reason") == "length"
|
if any(isinstance(c, dict) and c.get("finish_reason") == "length"
|
||||||
for c in resp_json.get("choices") or []):
|
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)
|
return JSONResponse(resp_json, status_code=r.status_code, headers=routed)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,16 @@ log = logging.getLogger(__name__)
|
|||||||
_trunc_last: dict[str, float] = {}
|
_trunc_last: dict[str, float] = {}
|
||||||
_TRUNC_EVERY = 600.0 # s
|
_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()
|
now = time.time()
|
||||||
if now - _trunc_last.get(model, 0.0) < _TRUNC_EVERY:
|
if now - _trunc_last.get(model, 0.0) < _TRUNC_EVERY:
|
||||||
return
|
return
|
||||||
@@ -50,11 +58,11 @@ def record_usage(usage: dict | None, model: str) -> None:
|
|||||||
increment_tokens(prompt, completion, model=model)
|
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
|
"""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:
|
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:
|
if b'"usage"' not in chunk:
|
||||||
return
|
return
|
||||||
text = chunk.decode("utf-8", errors="ignore")
|
text = chunk.decode("utf-8", errors="ignore")
|
||||||
|
|||||||
Reference in New Issue
Block a user