Report llama context limit errors clearly

This commit is contained in:
Mikei386
2026-09-30 07:15:46 +02:00
parent 36e1883657
commit dfe3c4398c
2 changed files with 33 additions and 2 deletions
+19 -1
View File
@@ -19,6 +19,24 @@ class APIError(ValueError):
def __init__(self,message,status=400,code='invalid_request_error'): def __init__(self,message,status=400,code='invalid_request_error'):
super().__init__(message);self.status=status;self.code=code super().__init__(message);self.status=status;self.code=code
def chat_upstream_error(status, raw):
"""Expose known, safe llama.cpp limits without reflecting request content."""
try:
detail=json.loads(raw)
error=detail.get('error',{}) if isinstance(detail,dict) else {}
if error.get('type')=='exceed_context_size_error':
needed=error.get('n_prompt_tokens');available=error.get('n_ctx')
if all(type(value) is int and 0<value<=2097152 for value in (needed,available)):
needed_text=f'{needed:,}'.replace(',','.');available_text=f'{available:,}'.replace(',','.')
return APIError(
f'Die Anfrage benötigt {needed_text} Kontext-Tokens, das aktive Profil bietet {available_text}. '
'Kontext im Profil erhöhen oder die Anfrage verkürzen.',
400,'context_length_exceeded')
except (ValueError,TypeError,AttributeError):
pass
return APIError('llama.cpp hat die Anfrage abgelehnt. Kontext und Anfrageparameter prüfen.',
status if 400<=status<600 else 502,'upstream_error')
class Endpoint: class Endpoint:
def __init__(self,root,profiles,worker,scheduler,images,credentials,management_port): def __init__(self,root,profiles,worker,scheduler,images,credentials,management_port):
self.root=Path(root);self.profiles=profiles;self.worker=worker;self.scheduler=scheduler;self.images=images;self.tts=None;self.stt=None;self.video=None;self.credentials=credentials self.root=Path(root);self.profiles=profiles;self.worker=worker;self.scheduler=scheduler;self.images=images;self.tts=None;self.stt=None;self.video=None;self.credentials=credentials
@@ -287,7 +305,7 @@ class APIHandler(BaseHTTPRequestHandler):
conn.request('POST','/v1/chat/completions',body=json.dumps(body).encode(),headers={'Content-Type':'application/json','Authorization':'Bearer '+key}) conn.request('POST','/v1/chat/completions',body=json.dumps(body).encode(),headers={'Content-Type':'application/json','Authorization':'Bearer '+key})
response=conn.getresponse() response=conn.getresponse()
if response.status!=200: if response.status!=200:
response.read(65536);raise APIError('llama.cpp hat die Anfrage abgelehnt. Kontext und Anfrageparameter prüfen.',response.status if 400<=response.status<600 else 502,'upstream_error') raise chat_upstream_error(response.status,response.read(65536))
if not data.get('stream'): if not data.get('stream'):
raw=response.read(16*1024*1024+1) raw=response.read(16*1024*1024+1)
if len(raw)>16*1024*1024:raise InferenceError('Modellantwort überschreitet 16 MiB.') if len(raw)>16*1024*1024:raise InferenceError('Modellantwort überschreitet 16 MiB.')
+14 -1
View File
@@ -11,7 +11,7 @@ from pathlib import Path
from types import SimpleNamespace from types import SimpleNamespace
from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer
from unittest.mock import Mock from unittest.mock import Mock
from endpoint import Endpoint from endpoint import Endpoint,chat_upstream_error
from inference import Scheduler,InferenceError from inference import Scheduler,InferenceError
class FakeWorker: class FakeWorker:
@@ -32,6 +32,19 @@ class Upstream(BaseHTTPRequestHandler):
else: else:
body=json.dumps(dict(id='test',model=data['model'],choices=[{'message':{'role':'assistant','content':'synthetic'}}])).encode();self.send_response(200);self.send_header('Content-Length',str(len(body)));self.end_headers();self.wfile.write(body) body=json.dumps(dict(id='test',model=data['model'],choices=[{'message':{'role':'assistant','content':'synthetic'}}])).encode();self.send_response(200);self.send_header('Content-Length',str(len(body)));self.end_headers();self.wfile.write(body)
class UpstreamErrorTests(unittest.TestCase):
def test_context_limit_is_actionable_without_echoing_upstream_text(self):
raw=json.dumps({'error':{'type':'exceed_context_size_error','message':'private request text',
'n_prompt_tokens':13523,'n_ctx':8192}}).encode()
error=chat_upstream_error(400,raw)
self.assertEqual(error.code,'context_length_exceeded')
self.assertIn('13.523',str(error));self.assertIn('8.192',str(error))
self.assertNotIn('private request text',str(error))
def test_unknown_upstream_errors_stay_generic(self):
error=chat_upstream_error(400,b'{"error":{"message":"private request text"}}')
self.assertEqual(error.code,'upstream_error')
self.assertNotIn('private request text',str(error))
class EndpointTests(unittest.TestCase): class EndpointTests(unittest.TestCase):
def setUp(self): def setUp(self):
self.tmp=tempfile.TemporaryDirectory();self.worker=FakeWorker();self.worker.http=ThreadingHTTPServer(('127.0.0.1',0),Upstream);self.worker.http.worker=self.worker;threading.Thread(target=self.worker.http.serve_forever,daemon=True).start() self.tmp=tempfile.TemporaryDirectory();self.worker=FakeWorker();self.worker.http=ThreadingHTTPServer(('127.0.0.1',0),Upstream);self.worker.http.worker=self.worker;threading.Thread(target=self.worker.http.serve_forever,daemon=True).start()